claude-overnight 1.11.3 → 1.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/render.js CHANGED
@@ -80,10 +80,9 @@ function renderUsageBars(out, w, swarm) {
80
80
  }
81
81
  let label = `${Math.round(pct * 100)}% used`;
82
82
  if (swarm.cappedOut) {
83
- if (swarm.isUsingOverage && !swarm.allowExtraUsage)
84
- label = chalk.red("Extra usage blocked \u2014 stopping");
85
- else
86
- label = chalk.yellow(`Capped at ${capFrac != null ? Math.round(capFrac * 100) : 100}% \u2014 finishing active`);
83
+ label = swarm.isUsingOverage
84
+ ? chalk.red(`Extra usage budget exhausted \u2014 finishing active`)
85
+ : chalk.yellow(`Capped at ${capFrac != null ? Math.round(capFrac * 100) : 100}% \u2014 finishing active`);
87
86
  }
88
87
  else if (swarm.rateLimitPaused > 0) {
89
88
  label = chalk.yellow(`Cooling down \u2014 ${swarm.rateLimitPaused} worker(s) waiting`);
package/dist/run.js CHANGED
@@ -390,7 +390,7 @@ export async function executeRun(cfg) {
390
390
  else if (remaining <= 0)
391
391
  console.log(chalk.bold.yellow(` CLAUDE OVERNIGHT — BUDGET EXHAUSTED`));
392
392
  else if (lastCapped)
393
- console.log(chalk.bold.yellow(` CLAUDE OVERNIGHT — RATE LIMITED`));
393
+ console.log(chalk.bold.yellow(` CLAUDE OVERNIGHT — EXTRA USAGE BUDGET HIT`));
394
394
  else if (stopping || lastAborted)
395
395
  console.log(chalk.bold.yellow(` CLAUDE OVERNIGHT — INTERRUPTED`));
396
396
  else
@@ -406,7 +406,7 @@ export async function executeRun(cfg) {
406
406
  for (const [k1, v1, k2, v2] of statRows)
407
407
  console.log(` ${k1} ${v1.padEnd(20)} ${k2} ${v2}`);
408
408
  if (lastCapped)
409
- console.log(` ${chalk.yellow(`Capped at ${usageCap != null ? Math.round(usageCap * 100) : 100}%`)}`);
409
+ console.log(` ${chalk.yellow(`Extra usage budget exhausted`)}`);
410
410
  console.log("");
411
411
  const statusFile = join(runDir, "status.md");
412
412
  if (existsSync(statusFile)) {
package/dist/swarm.d.ts CHANGED
@@ -40,7 +40,6 @@ export declare class Swarm {
40
40
  rateLimitWindows: Map<string, RateLimitWindow>;
41
41
  rateLimitPaused: number;
42
42
  isUsingOverage: boolean;
43
- overageDisabledReason?: string;
44
43
  overageCostUsd: number;
45
44
  private queue;
46
45
  private config;
package/dist/swarm.js CHANGED
@@ -33,7 +33,6 @@ export class Swarm {
33
33
  rateLimitWindows = new Map();
34
34
  rateLimitPaused = 0;
35
35
  isUsingOverage = false;
36
- overageDisabledReason;
37
36
  overageCostUsd = 0;
38
37
  queue;
39
38
  config;
@@ -180,43 +179,45 @@ export class Swarm {
180
179
  async throttle() {
181
180
  if (this.cappedOut)
182
181
  return;
183
- if (this.isUsingOverage && !this.allowExtraUsage) {
184
- this.capForOverage(`Extra usage detected but not allowed — stopping dispatch`);
185
- return;
186
- }
182
+ // Hard stop: overage budget exhausted (only legitimate cap)
187
183
  if (this.isUsingOverage && this.extraUsageBudget != null && this.overageCostUsd >= this.extraUsageBudget) {
188
184
  this.capForOverage(`Extra usage budget $${this.extraUsageBudget} reached ($${this.overageCostUsd.toFixed(2)} spent) — stopping dispatch`);
189
185
  return;
190
186
  }
191
- const cap = this.usageCap;
192
- if (cap != null && cap < 1 && this.rateLimitUtilization >= cap) {
193
- const waitMs = this.rateLimitResetsAt
187
+ // Wait loop: keep waiting until the blocking condition clears
188
+ let consecutiveWaits = 0;
189
+ for (;;) {
190
+ if (this.aborted || this.cappedOut)
191
+ return;
192
+ const cap = this.usageCap;
193
+ const overageBlocked = this.isUsingOverage && !this.allowExtraUsage;
194
+ const capExceeded = cap != null && cap < 1 && this.rateLimitUtilization >= cap;
195
+ const rejected = this.rateLimitResetsAt && this.rateLimitResetsAt > Date.now();
196
+ if (!overageBlocked && !capExceeded && !rejected)
197
+ break;
198
+ // Use SDK reset time if available, otherwise escalate: 1m → 3m → 5m (max)
199
+ const fallbackMs = Math.min(300_000, 60_000 * (1 + consecutiveWaits * 2));
200
+ const waitMs = this.rateLimitResetsAt && this.rateLimitResetsAt > Date.now()
194
201
  ? Math.max(5000, this.rateLimitResetsAt - Date.now())
195
- : 60_000;
196
- this.log(-1, `Usage at ${Math.round(this.rateLimitUtilization * 100)}% (cap ${Math.round(cap * 100)}%), waiting ${Math.ceil(waitMs / 1000)}s for cooldown`);
202
+ : fallbackMs;
203
+ const reason = overageBlocked ? "Extra usage blocked (plan limit)"
204
+ : capExceeded ? `Usage at ${Math.round(this.rateLimitUtilization * 100)}% (cap ${Math.round(cap * 100)}%)`
205
+ : "Rate limited";
206
+ this.log(-1, `${reason} — waiting ${Math.ceil(waitMs / 1000)}s then retrying`);
197
207
  this.rateLimitPaused++;
198
208
  await sleep(waitMs);
199
209
  this.rateLimitPaused--;
210
+ // Reset stale flags — fresh state will come from the next SDK event
211
+ this.isUsingOverage = false;
200
212
  this.rateLimitUtilization = 0;
201
213
  this.rateLimitResetsAt = undefined;
202
- return;
203
- }
204
- if (this.rateLimitResetsAt) {
205
- const resetTarget = this.rateLimitResetsAt;
206
- const waitMs = resetTarget - Date.now();
207
- if (waitMs > 0) {
208
- this.log(-1, `Rate limited, pausing ${Math.ceil(waitMs / 1000)}s`);
209
- this.rateLimitPaused++;
210
- await sleep(waitMs);
211
- this.rateLimitPaused--;
212
- }
213
- if (this.rateLimitResetsAt === resetTarget)
214
- this.rateLimitResetsAt = undefined;
214
+ consecutiveWaits++;
215
215
  }
216
- else if (this.rateLimitUtilization > 0.75) {
216
+ // Soft delay: high utilization, pace requests
217
+ if (this.rateLimitUtilization > 0.75) {
217
218
  const delay = Math.floor((this.rateLimitUtilization - 0.75) * 60000);
218
- this.log(-1, `Soft throttle: ${Math.round(this.rateLimitUtilization * 100)}% utilization, pausing ${(delay / 1000).toFixed(1)}s`);
219
- await sleep(delay);
219
+ if (delay > 0)
220
+ await sleep(delay);
220
221
  }
221
222
  }
222
223
  // ── Agent execution ──
@@ -370,6 +371,21 @@ export class Swarm {
370
371
  catch (err) {
371
372
  if (agent.status !== "running")
372
373
  break;
374
+ // Rate-limit errors: wait and retry WITHOUT burning the retry budget
375
+ if (!this.aborted && isRateLimitError(err)) {
376
+ const waitMs = this.rateLimitResetsAt && this.rateLimitResetsAt > Date.now()
377
+ ? Math.max(5000, this.rateLimitResetsAt - Date.now())
378
+ : 120_000;
379
+ this.log(id, `Rate limited — waiting ${Math.ceil(waitMs / 1000)}s (attempt not counted)`);
380
+ this.rateLimitPaused++;
381
+ await sleep(waitMs);
382
+ this.rateLimitPaused--;
383
+ this.isUsingOverage = false;
384
+ this.rateLimitUtilization = 0;
385
+ this.rateLimitResetsAt = undefined;
386
+ attempt--; // don't count this against retries
387
+ continue;
388
+ }
373
389
  const canRetry = attempt < maxRetries && !this.aborted && isTransientError(err);
374
390
  if (canRetry) {
375
391
  this.log(id, `Transient error: ${String(err.message || err).slice(0, 80)}`);
@@ -460,26 +476,21 @@ export class Swarm {
460
476
  const rl = msg;
461
477
  const info = rl.rate_limit_info;
462
478
  this.rateLimitUtilization = info.utilization ?? 0;
463
- if (info.status === "rejected" && info.resetsAt && !this.allowExtraUsage)
479
+ if (info.resetsAt)
464
480
  this.rateLimitResetsAt = info.resetsAt;
465
481
  else if (info.status !== "rejected")
466
482
  this.rateLimitResetsAt = undefined;
483
+ if (info.isUsingOverage)
484
+ this.isUsingOverage = true;
467
485
  const windowType = info.rateLimitType;
468
486
  if (windowType) {
469
487
  this.rateLimitWindows.set(windowType, {
470
488
  type: windowType, utilization: info.utilization ?? 0, status: info.status, resetsAt: info.resetsAt,
471
489
  });
472
490
  }
473
- if (info.isUsingOverage)
474
- this.isUsingOverage = true;
475
- if (info.overageDisabledReason)
476
- this.overageDisabledReason = info.overageDisabledReason;
477
- if (this.isUsingOverage && !this.allowExtraUsage)
478
- this.capForOverage(`Extra usage detected but not allowed — stopping dispatch`);
479
491
  const pct = info.utilization != null ? `${Math.round(info.utilization * 100)}%` : "";
480
492
  const overageTag = this.isUsingOverage ? " [EXTRA]" : "";
481
- const statusLabel = info.status === "rejected" && this.allowExtraUsage ? "switching to extra usage" : info.status;
482
- this.log(agent.id, `Rate: ${statusLabel} ${pct}${overageTag}${windowType ? ` (${windowType})` : ""}`);
493
+ this.log(agent.id, `Rate: ${info.status} ${pct}${overageTag}${windowType ? ` (${windowType})` : ""}`);
483
494
  break;
484
495
  }
485
496
  }
@@ -491,6 +502,18 @@ class AgentTimeoutError extends Error {
491
502
  this.name = "AgentTimeoutError";
492
503
  }
493
504
  }
505
+ function isRateLimitError(err) {
506
+ const status = err?.status ?? err?.statusCode;
507
+ if (status === 429)
508
+ return true;
509
+ const msg = String(err?.message || err).toLowerCase();
510
+ if (msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("too many requests"))
511
+ return true;
512
+ const cause = err?.cause;
513
+ if (cause && cause !== err)
514
+ return isRateLimitError(cause);
515
+ return false;
516
+ }
494
517
  function isTransientError(err) {
495
518
  if (err instanceof AgentTimeoutError)
496
519
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.11.3",
3
+ "version": "1.11.5",
4
4
  "description": "Run 10, 100, or 1000 Claude agents overnight. Parallel autonomous AI coding with thinking waves, iterative quality steering, crash recovery, and rate limit handling. Built on the Claude Agent SDK.",
5
5
  "type": "module",
6
6
  "bin": {