@link-assistant/hive-mind 2.0.0 → 2.0.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 70e1542: fix(retry): treat 5-hour "session limit" and "weekly limit" 429s as account usage limits, not transient throttles (#1935)
8
+
9
+ A long-running solve session (588 turns, ~$70.62) hit Claude's **5-hour session
10
+ limit**. The Claude CLI surfaced it as a `result` event with `is_error: true`,
11
+ `api_error_status: 429`, and:
12
+
13
+ ```
14
+ You've hit your session limit · resets 4pm (UTC)
15
+ ```
16
+
17
+ Instead of being treated as an **account usage limit** (post a comment with the
18
+ reset time + wait until the exact reset moment), it was put through the transient
19
+ exponential-backoff retry loop:
20
+
21
+ ```
22
+ ⚠️ Server rate limited (429) detected. Retry 1/10 in 2 min (session preserved)...
23
+ Error: You've hit your session limit · resets 4pm (UTC)
24
+ ⚠️ Server rate limited (429) detected. Retry 2/10 in 4 min (session preserved)...
25
+ ```
26
+
27
+ Each retry re-hit the same limit because the quota only frees at the reset time —
28
+ so the harness burned ~10 futile retries and never told the user when the limit
29
+ resets.
30
+
31
+ Root cause (regression from #1924): `src/claude.lib.mjs` set
32
+ `isRateLimitError = true` for **every** structured `api_error_status === 429`,
33
+ without checking whether the message was an account usage limit. Claude reports
34
+ **both** a transient throttle ("...not your usage limit...") and account
35
+ session/weekly limits with `api_error_status: 429`, so the unconditional check
36
+ swept genuine usage limits into the transient-retry path — ahead of the
37
+ `detectUsageLimit()` reset-time wait, which was therefore never reached.
38
+
39
+ Fix: `src/claude.lib.mjs` now only flags a structured 429 as a transient rate
40
+ limit when the message is **not** a usage limit
41
+ (`api_error_status === 429 && !isUsageLimitError(lastMessage)`), so session/weekly
42
+ limits fall through to the usage-limit handler that immediately posts a comment
43
+ and waits until the exact reset time (auto-resuming there with
44
+ `--auto-continue-limit`). `src/usage-limit.lib.mjs` additionally recognises the
45
+ "hit your session limit" / "hit your weekly limit" phrasing as a backstop (the
46
+ reset-time regex already matched "resets 4pm").
47
+
48
+ Added `tests/test-issue-1935-session-limit-429.mjs` (15 assertions) and a full
49
+ case study with timeline, blame history (PR #1924), root-cause analysis, and the
50
+ captured logs under `docs/case-studies/issue-1935`.
51
+
3
52
  ## 2.0.0
4
53
 
5
54
  ### Major Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ const path = (await use('path')).default;
9
9
  import { log, isENOSPC } from './lib.mjs';
10
10
  import { reportError } from './sentry.lib.mjs';
11
11
  import { timeouts, retryLimits, claudeCode, getClaudeEnv, getThinkingLevelToTokens, getTokensToThinkingLevel, supportsThinkingBudget, DEFAULT_MAX_THINKING_BUDGET, getMaxOutputTokensForModel } from './config.lib.mjs';
12
- import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
12
+ import { detectUsageLimit, formatUsageLimitMessage, isUsageLimitError } from './usage-limit.lib.mjs';
13
13
  import { createInteractiveHandler } from './interactive-mode.lib.mjs';
14
14
  import { setupBidirectionalHandler, finalizeBidirectionalHandler, validateBidirectionalModeConfig, attachStreamingInput } from './bidirectional-interactive.lib.mjs';
15
15
  import { initProgressMonitoring } from './solve.progress-monitoring.lib.mjs';
@@ -978,11 +978,12 @@ export const executeClaudeCommand = async params => {
978
978
  isRequestTimeout = true;
979
979
  await log('⏱️ Detected request timeout from Claude CLI (will retry with --resume)', { verbose: true });
980
980
  }
981
- // Issue #1924: Server-side temporary rate limiting (HTTP 429) a transient
982
- // throttle, not an account usage limit ("...not your usage limit..."), so retry
983
- // with --resume. The message text is handled by classifyRetryableError; this also
984
- // catches the structured api_error_status if the wording ever changes.
985
- if (data.api_error_status === 429) {
981
+ // Issue #1924: server-side temporary rate limiting (HTTP 429) is a transient
982
+ // throttle ("...not your usage limit..."), so retry with --resume. Issue #1935
983
+ // (regression from #1924): account usage limits ("session limit" / "weekly limit")
984
+ // ALSO arrive with api_error_status === 429 plus an explicit reset time, so the
985
+ // isUsageLimitError() guard routes those to the usage-limit handler below instead.
986
+ if (data.api_error_status === 429 && !isUsageLimitError(lastMessage)) {
986
987
  isRateLimitError = true;
987
988
  await log(`⚠️ Detected server-side rate limiting (429) from Claude CLI (will retry with --resume). request_id=${data.request_id || 'unknown'}`, { verbose: true });
988
989
  }
@@ -48,6 +48,14 @@ export function isUsageLimitError(message) {
48
48
  // Provider-specific phrasings we've seen in the wild
49
49
  'session limit reached', // Claude
50
50
  'weekly limit reached', // Claude
51
+ // Issue #1935: Claude surfaces 5-hour / weekly account limits as
52
+ // "You've hit your session limit · resets 4pm (UTC)"
53
+ // "You've hit your weekly limit · resets Jan 15, 8am (UTC)"
54
+ // These arrive with api_error_status === 429 but are real usage limits with an
55
+ // explicit reset time, so detect the "hit your <window> limit" phrasing directly
56
+ // (independent of whether a parseable reset time is present in the message).
57
+ 'hit your session limit', // Claude 5-hour limit
58
+ 'hit your weekly limit', // Claude weekly limit
51
59
  'daily limit reached',
52
60
  'monthly limit reached',
53
61
  'billing hard limit',