Hourly submit limit counts only accepted submissions
Refused submissions (too many batches waiting) counted against the 30 per hour, so an agent retrying while its queue was full used up the hour and stayed locked out after the queue cleared. Refusals now say when to retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW
This commit is contained in:
parent
3c946d7689
commit
e3fda917c5
3 changed files with 23 additions and 3 deletions
|
|
@ -398,10 +398,11 @@ export function createApp(ctx) {
|
|||
|
||||
router.on('POST', '/api/tasks/:id/submit', async ({ req, params }) => {
|
||||
const user = auth(req);
|
||||
submitLimiter.check(user.id);
|
||||
submitLimiter.ensure(user.id);
|
||||
const taskId = intParam(params.id);
|
||||
const body = await readJson(req);
|
||||
const r = submitBatch(user, [taskId], body);
|
||||
submitLimiter.record(user.id);
|
||||
return guided({ attemptId: r.attempts[0].attemptId, batchId: r.batchId, state: 'queued' });
|
||||
});
|
||||
|
||||
|
|
@ -460,9 +461,10 @@ export function createApp(ctx) {
|
|||
|
||||
router.on('POST', '/api/batches', async ({ req }) => {
|
||||
const user = auth(req);
|
||||
submitLimiter.check(user.id);
|
||||
submitLimiter.ensure(user.id);
|
||||
const body = await readJson(req, 8 * 1024 * 1024);
|
||||
const r = submitBatch(user, (body.taskIds ?? []).map((x) => intParam(x)), body);
|
||||
submitLimiter.record(user.id);
|
||||
return guided({ ...r, state: 'queued' });
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -78,4 +78,22 @@ export class RateLimiter {
|
|||
if (e.n > this.max) throw new HttpError(429, 'rate limit exceeded, try again later');
|
||||
if (this.hits.size > 10000) for (const [k, v] of this.hits) if (t - v.start > this.windowMs) this.hits.delete(k);
|
||||
}
|
||||
|
||||
// For limits on work that can be refused for other reasons: `ensure` before, `record` only once
|
||||
// it was accepted, so a client retrying a refused request does not use up its budget.
|
||||
ensure(key) {
|
||||
const e = this.hits.get(key);
|
||||
const t = Date.now();
|
||||
if (e && t - e.start <= this.windowMs && e.n >= this.max) {
|
||||
const wait = Math.ceil((e.start + this.windowMs - t) / 1000);
|
||||
throw new HttpError(429, `rate limit exceeded (${this.max} per ${Math.round(this.windowMs / 60_000)} minutes); try again in ${Math.ceil(wait / 60)} minute(s)`, { retryAfterSeconds: wait });
|
||||
}
|
||||
}
|
||||
|
||||
record(key) {
|
||||
const t = Date.now();
|
||||
let e = this.hits.get(key);
|
||||
if (!e || t - e.start > this.windowMs) this.hits.set(key, (e = { start: t, n: 0 }));
|
||||
e.n++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ export class Tasks {
|
|||
const max = this.config.maxQueuedBatches ?? 4;
|
||||
const waiting = this.store.get(`SELECT COUNT(*) AS n FROM batches WHERE user_id = ? AND state IN ('queued', 'verifying')`, user.id).n;
|
||||
if (waiting >= max) {
|
||||
throw new HttpError(429, `you have ${waiting} batches waiting for verification (the limit is ${max}); keep your claims, do peer reviews meanwhile (td_list_peer_reviews), and submit again when one is done`);
|
||||
throw new HttpError(429, `you have ${waiting} batches waiting for verification (the limit is ${max}); keep your claims, do peer reviews meanwhile (td_list_peer_reviews), and submit again when one is done (td_get_batch shows it)`, { retryAfterSeconds: 120 });
|
||||
}
|
||||
const b = this.store.run(
|
||||
`INSERT INTO batches (user_id, base_commit, files_json, notes, agent_meta, env_digest, state, created_at) VALUES (?, ?, ?, ?, ?, ?, 'queued', ?)`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue