Conversation
…payload
Internal response.failed envelopes carry the classified {type, code, message}
but no numeric status, and the Claude outbound defaulted every such failure to
500 — inside the transient set — so a Cursor plan/quota 429 reached Claude Code
as a retryable overloaded_error and surfaced as "Repeated 529 Overloaded
errors" while /api/logs correctly recorded 429. Derive the missing status with
httpStatusFromTerminalError, the same mapping the request log uses, so
rate-limit, auth, and invalid-request failures keep their real Anthropic error
type. Unclassified synthetic tails still land on transient 5xx and map to
overloaded_error as designed.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
✅ Deterministic PR hygiene checks passed. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughChangesTerminal failure mapping
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to This focused error-mapping change is merge-ready after normal checks and review; no actionable merge-blocking risk remains. Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88d1da6b30
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| : httpStatusFromTerminalError({ | ||
| type: typeof error.type === "string" ? error.type : undefined, | ||
| code: typeof error.code === "string" ? error.code : null, | ||
| message, | ||
| }); |
There was a problem hiding this comment.
Preserve generic server failures as retryable
When a status-less failure is classified as server_error / upstream_server_error, this helper falls through to message-based inference, which can override the structured classification. For example, the malformed-tool-arguments failure emitted by src/bridge.ts contains “malformed”; httpStatusFromTerminalError consequently returns 400, and Claude Code receives invalid_request_error instead of the previous retryable overloaded_error. Prefer mapping the generic server/upstream classification to a transient 5xx before consulting message keywords, while retaining the specific rate-limit/auth/permission mappings.
AGENTS.md reference: src/AGENTS.md:L19-L19
Useful? React with 👍 / 👎.
리뷰 · 우선순위 68 / 80지금
이 PR은 빈 라인 단위로 보면 현재 라인 outbound.ts 553-558 - 라인 outbound.ts 562-566 - 경로 tests/claude-outbound.test.ts - 분류된 429/401/400과 진짜 overload는 있다. 빈 경로 PR 본문 bun test - 작성자가 로컬에서 다른 파일 실패가 난다고 적었다. stash 하면 같은 실패가 난다고 했다. 그 말은 이 PR 회귀가 아니라는 주장이다. 머지 판단은 로컬이 아니라 CI를 본다. 메인테이너의 판단이 필요한 지점
너의 추천 이 댓글은 grok-bot이 작성했습니다 |
…request A plan-gated model invoked on a plan without it (live: cursor/claude-fable-5) is rejected with "Cursor Connect error failed_precondition", which fell through to "Cursor upstream error" (502) — a transient status the Claude inbound then surfaced as a retryable 529 overloaded_error. gRPC FAILED_PRECONDITION is deterministic and non-retryable by definition, so classify it as "Cursor invalid request" (400) and let the client see the real verdict immediately. Co-authored-by: Cursor <cursoragent@cursor.com>
Ingwannu
left a comment
There was a problem hiding this comment.
Requesting changes at exact head 19801d201d8152684dec8bf8e44b7f3571414270.
The main diagnosis is valid and the Cursor failed_precondition -> 400 classification is correct. My isolated focused run passed 157/157 across the eight Claude/Cursor error suites. One concrete error-fidelity regression remains, and the current green tests do not cover it.
httpStatusFromTerminalError recognizes only server_error + server_is_overloaded before falling through to message inference. A status-less terminal envelope such as {type:"server_error", code:"upstream_server_error", message:"upstream stream produced malformed tool call arguments"} therefore returns 400 because the message contains malformed. An exact-head probe returned 400. Before this PR, the absent numeric status became transient 500; after this PR Claude Code receives invalid_request_error and stops retrying a genuine upstream/server failure.
Preserve structured generic server classifications before message keywords: map server_error / upstream_server_error and equivalent generic upstream codes to the intended transient 5xx, while retaining the specific 429, 401, 403, invalid-request, policy, cancellation, and explicit overload mappings. Add a status-less regression using a server-classified message that contains an invalid-request keyword and assert that the Anthropic tail remains overloaded_error. The unclassified synthetic-tail regression described in the PR should also be explicit rather than comment-only.
The dead translation_buffer_limit status arm and loss of the original error code can remain bounded follow-ups; neither is the blocker above. This branch is currently 87 commits behind dev@7ca954ffd997197d1cff6fc6d69842be51177a8f, so rebase the focused fix and rerun exact-head CI after adding the regression. The PR remains valuable once the structured server class wins over message heuristics.
|
Superseded by #2769, which carries both of your commits unmodified onto current Your diagnosis was correct, and the review's objection was also correct — they compose into one finding worth recording. Deriving the status from the classified payload is the right fix, but it only helps if the classification actually wins downstream, and it did not: reproduced against unpatched #2769 adds one commit closing that, narrowed to the 400 verdict only. My first attempt returned a blanket 502 and broke Evidence on the superseding branch: full suite 15349 pass / 0 fail, nine consuming focused suites 210/0, Closing this one as superseded rather than merged, so the record points at the branch that carries the complete change. Your commits are preserved with authorship intact. |
Adversarial reviewer (Sol high) returned FAIL on two counts, both correct. 1. The plan reproduced the unfixed #2745 OAuth credential-boundary defect, its activation sequence and its remediation inside devlog/ — a public tracked directory — while the PR is still open. AGENTS.md forbids exactly that. The analysis moves to .tmp/ (gitignored) and the tracked doc keeps only the disposition line and a pointer to the PR. 2. Every merge lane went straight from 'CI green' to 'merge', skipping the non-author maintainer approval MAINTAINERS.md requires. All four Ingwannu PRs read REVIEW_REQUIRED, and package.json is a restricted surface per .github/scripts/pr-sponsored-surface.cjs. A round-level instruction is not an exact-head approval; each lane now carries the gate explicitly, and #2766 exiting as NEEDS_HUMAN(approval) is recorded as a real terminal state. Also: narrowed the wp2 barrier to merges only (review and rebases can run in parallel — disjoint paths), recorded mutation-test evidence that the #2733, #2726 and #2761 regressions are load-bearing, and replaced the overbroad 'No auth surface' line on #2729 with the precise boundary.
#2729's fix only helps if the classification actually wins, and it did not: httpStatusFromTerminalError recognized one code pair and let a generic upstream_server_error fall through to message inference, returning 400 for an upstream 5xx whose text contained 'malformed'. My first repair returned a blanket 502 and passed nine focused suites — 210/0 — while breaking two CI shards, because a web-search stall genuinely is 504 and flattening it discards information. The override narrowed to the 400 verdict alone. The transferable finding: green TARGETED checks are not health either, because you chose the targets. The differential probe against unpatched dev is what made the recovery cheap.
Final-round auditor found the #2729 failed_precondition arm did not fire on the shape it exists to catch. It sat AFTER the overload keywords in both classifyCursorError and inferHttpStatusFromAdapterMessage, and a plan-gated rejection normally reads 'failed_precondition: model unavailable for this plan' — which matched 'unavailable' first: classifyCursorError -> 'Cursor server overloaded' inferHttpStatus... -> 503 So clients kept retrying a deterministic rejection that can never succeed, which is the exact misclassification the arm was added to stop. The original test only covered 'Cursor Connect error failed_precondition: Error', which has no competing keyword, so 25/25 passed over the defect. Moved the check ahead of the overload keywords in both functions. The explicit gRPC status is a structured backend signal; 'unavailable' and 'temporarily' next to it are inference over free text. Differential vs origin/dev: exactly one case changes (fp + unavailable, 503 -> 400). Real overloads, auth, rate limit, timeout and invalid-request are all unchanged, and authentication still outranks both. 211 pass / 0 fail across the ten affected suites; tsc exit 0.
Adversarial reviewer (Sol high) returned FAIL on two counts, both correct. 1. The plan reproduced the unfixed #2745 OAuth credential-boundary defect, its activation sequence and its remediation inside devlog/ — a public tracked directory — while the PR is still open. AGENTS.md forbids exactly that. The analysis moves to .tmp/ (gitignored) and the tracked doc keeps only the disposition line and a pointer to the PR. 2. Every merge lane went straight from 'CI green' to 'merge', skipping the non-author maintainer approval MAINTAINERS.md requires. All four Ingwannu PRs read REVIEW_REQUIRED, and package.json is a restricted surface per .github/scripts/pr-sponsored-surface.cjs. A round-level instruction is not an exact-head approval; each lane now carries the gate explicitly, and #2766 exiting as NEEDS_HUMAN(approval) is recorded as a real terminal state. Also: narrowed the wp2 barrier to merges only (review and rebases can run in parallel — disjoint paths), recorded mutation-test evidence that the #2733, #2726 and #2761 regressions are load-bearing, and replaced the overbroad 'No auth surface' line on #2729 with the precise boundary.
#2729's fix only helps if the classification actually wins, and it did not: httpStatusFromTerminalError recognized one code pair and let a generic upstream_server_error fall through to message inference, returning 400 for an upstream 5xx whose text contained 'malformed'. My first repair returned a blanket 502 and passed nine focused suites — 210/0 — while breaking two CI shards, because a web-search stall genuinely is 504 and flattening it discards information. The override narrowed to the 400 verdict alone. The transferable finding: green TARGETED checks are not health either, because you chose the targets. The differential probe against unpatched dev is what made the recovery cheap.
… false-abort a healthy turn (#2774) * docs(devlog): plan the igwanu bug-PR merge round Intake for the 13 open bug-labelled PRs. Records the merged-tree compile gate (12 clean, #2497 conflicting), the pairwise file-contention map, and the finding that orders the round: #2767, #2764 and #2747 fail required CI on a shared repository-wide assertion (package.json 2.34.0 == released tag v2.34.0), not on their own code. #2766 repairs it and is the keystone. * docs(devlog): keep the round plan out of its own privacy-scan finding The roadmap documented the scp-style SSH literal by quoting it, which reproduced the exact privacy:scan failure the plan says #2766 repairs. Describe the remote instead of quoting it; devlog/ is scanned. * docs(devlog): split the shared CI failure per job, per PR A-gate reviewer (Sol high) found the per-PR accounting collapsed two distinct shared defects into one. test 3/4 and macos fail on release-version-line; gates fails on the privacy-scan runbook literal; ci is the fan-in. #2747 has no gates failure at all because its head predates the runbook doc. * docs(devlog): close the two A-gate blockers on the round plan Adversarial reviewer (Sol high) returned FAIL on two counts, both correct. 1. The plan reproduced the unfixed #2745 OAuth credential-boundary defect, its activation sequence and its remediation inside devlog/ — a public tracked directory — while the PR is still open. AGENTS.md forbids exactly that. The analysis moves to .tmp/ (gitignored) and the tracked doc keeps only the disposition line and a pointer to the PR. 2. Every merge lane went straight from 'CI green' to 'merge', skipping the non-author maintainer approval MAINTAINERS.md requires. All four Ingwannu PRs read REVIEW_REQUIRED, and package.json is a restricted surface per .github/scripts/pr-sponsored-surface.cjs. A round-level instruction is not an exact-head approval; each lane now carries the gate explicitly, and #2766 exiting as NEEDS_HUMAN(approval) is recorded as a real terminal state. Also: narrowed the wp2 barrier to merges only (review and rebases can run in parallel — disjoint paths), recorded mutation-test evidence that the #2733, #2726 and #2761 regressions are load-bearing, and replaced the overbroad 'No auth surface' line on #2729 with the precise boundary. * docs(devlog): remove the last pre-disclosure shape from the round plan Re-verification caught a third instance my first repair missed: the TESTS section still named the regression design for the unfixed #2745 defect, which carries the activation shape even without the prose. It now points at scratch. The reviewer also found the same mechanism in two OTHER open _plan units (260826_wp7e_presence_driven_oauth_failover, 260827_dev_hardening). Those are pre-existing and predate this round; recorded as a follow-up rather than silently rewritten here. * docs(devlog): lock the igwanu round roadmap after the A gate wp1 close-out. Records the four-round adversarial audit, both accepted blockers and their repairs, the resolved approval path (lidge-jun is a valid non-author approver for Ingwannu PRs per MAINTAINERS.md:57-59), and the keystone proof: 15334 pass / 0 fail on #2766's merged tree via ocx-run on lidge, with the release-version test and privacy scan green there and red on plain dev. * docs(devlog): record wp2 — keystone landed and hypothesis proven dev 8b1b65b -> 50e9556, six PRs merged (#2766 #2733 #2726 #2761 #2764 #2767). The keystone claim was tested rather than assumed: #2764 and #2767 were rebased onto post-#2766 dev with no change to their own diffs, and both went from four failing required jobs to 26 success / 0 failures. One version string and one doc line cleared red CI across three unrelated PRs. Also records why the A-gate approval blocker mattered, and the fork-branch constraint that makes rerunning CI the correct move for #2747. * docs(devlog): correct the #2747 claim in the wp2 record Post-execution auditor (Sol high) verified all six merges — first-parent count, patch identity across the rebase, exact-head approvers, post-merge dev CI — and found I overstated one thing. I wrote that four required jobs went green across THREE PRs and that #2747's rerun was 'in flight'. Neither was true. gh run rerun --failed replays the same commit, so #2747 attempt 4 completed red on the same 2.34.0/v2.34.0 collision, and it was already terminal ~70 seconds before I committed the claim. The keystone proof rests on #2764 and #2767 only; #2747 is diagnosed and awaiting an author rebase because its head is on a fork. Also notes #2766 carries two approval events (before and after a check re-run). * docs(devlog): fix two errors introduced by the previous correction Auditor FAILed my corrections, correctly. 1. The 'operational notes' still said re-running CI was the CORRECT action for a fork PR — directly contradicting the section I had just written explaining that a rerun replays the same commit. It now says plainly that neither option was available to me and only an author rebase moves #2747. 2. I claimed the two #2766 approvals straddled a check re-run. They did not: 15:27:49Z and 15:28:04Z, 15 seconds apart, with every head workflow already complete by 14:44:38Z. Replaced with the actual timestamps. An incorrect correction is worse than the original error. * docs(devlog): #2747 was a choice, not a constraint Third auditor FAIL on the same paragraph, and right again. I wrote that rewriting the contributor's branch was 'not available'. GitHub reports maintainerCanModify=true on #2747, so it was available the whole time. What actually happened is that I chose not to force-push a rebase onto work another contributor owns when the only defect was in our base. That is a judgement about ownership and it now reads as one, including the admission that an earlier draft dressed it up as a technical limit. * docs(devlog): record the #2729 supersede and the blanket-502 mistake #2729's fix only helps if the classification actually wins, and it did not: httpStatusFromTerminalError recognized one code pair and let a generic upstream_server_error fall through to message inference, returning 400 for an upstream 5xx whose text contained 'malformed'. My first repair returned a blanket 502 and passed nine focused suites — 210/0 — while breaking two CI shards, because a web-search stall genuinely is 504 and flattening it discards information. The override narrowed to the 400 verdict alone. The transferable finding: green TARGETED checks are not health either, because you chose the targets. The differential probe against unpatched dev is what made the recovery cheap. * docs(devlog): #2769 is NEEDS_HUMAN — I cannot approve my own PR GitHub refuses the review outright. The Ingwannu PRs were approvable because author and approver were different maintainers; here I am both, so the MAINTAINERS.md self-approval rule binds and is platform-enforced. Every technical gate is green (15349/0 full suite, required CI green at the exact head, mutation oracle held). The missing input is a second maintainer. * docs(devlog): round outcome — 13 bug PRs dispositioned Six merged, one closed as superseded, six open each with a named unblocking condition and the person who owns it. dev 8b1b65b -> 50e9556 through PRs only. Records the two gates this round adds: red checks are not harm until the shared baseline is green, and green TARGETED checks are not health either because you chose the targets. Also records the six reviewer FAILs, including the two about honesty rather than correctness. * docs(devlog): correct three claims the final audit disproved 1. '#2769: all gates green, approval is the only blocker' was false — an unresolved failed_precondition precedence defect was still open. Fixed in 16cb875 and the claim corrected rather than dropped. 2. The macOS CL-07 failure is UNATTRIBUTED, not proven flaky. Both arguments I used were wrong: the test does reach the changed code transitively, and the comparison failure on d1def68 was Linux, not macOS. 3. Behind-counts were stale by 13 commits — measured against the round's opening base rather than the dev its own merges produced. Now 131/192/399. * devlog(260828_cursor_ndjson_backlog_train): roadmap unit — backlog RCA + cursor defect inventory + phase docs * devlog(260828): fold A-gate round-1 blockers into roadmap (13 findings) * devlog(260828): roadmap lock — wp map bound to decade docs * devlog(260828): wp2 audit round-2 fold — combined-length coalescing threshold * fix(responses): coalesce buffered deltas so a stalled consumer cannot false-abort a healthy turn The runTurn backlog cap counts events, not tokens. A Codex app consumer mid-reconnect (Bun delivers the disconnect late) stopped pulling while the adapter kept streaming token-granular deltas, hit the 1024-event cap within seconds, and killed the turn with a fabricated adapter error. Adjacent same-phase text deltas, adjacent thinking deltas, and consecutive heartbeats now merge at push time when no reader is waiting (combined-length ceiling 64K code units; tail replaced, never mutated). The overflow message now names the real condition: a stalled consumer. * devlog(260828): fold wp3 runbook audit — isolated homes, no-refresh gate, direct startServer launcher, capture contract * devlog(260828): wp3 probe round 1 — N4 validates coalescing live; mid-stream envelope echo + mar corruption captured * devlog(260828): 031 diff spec — mid-stream envelope-echo detection (diagnostic-only round) * devlog(260828): strip remote home paths from 020 (privacy:scan) * test(cursor): give the discovery retry test a CI-proof timeout budget The 120ms budget applied to BOTH attempts (retry cap is min-ed with it); on a loaded runner the succeeding second attempt also timed out. 1s keeps the test deterministic without materially slowing the suite.
…payload (#2769) * fix(claude): derive response.failed status from the classified error payload Internal response.failed envelopes carry the classified {type, code, message} but no numeric status, and the Claude outbound defaulted every such failure to 500 — inside the transient set — so a Cursor plan/quota 429 reached Claude Code as a retryable overloaded_error and surfaced as "Repeated 529 Overloaded errors" while /api/logs correctly recorded 429. Derive the missing status with httpStatusFromTerminalError, the same mapping the request log uses, so rate-limit, auth, and invalid-request failures keep their real Anthropic error type. Unclassified synthetic tails still land on transient 5xx and map to overloaded_error as designed. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cursor): classify failed_precondition as a non-retryable invalid request A plan-gated model invoked on a plan without it (live: cursor/claude-fable-5) is rejected with "Cursor Connect error failed_precondition", which fell through to "Cursor upstream error" (502) — a transient status the Claude inbound then surfaced as a retryable 529 overloaded_error. gRPC FAILED_PRECONDITION is deterministic and non-retryable by definition, so classify it as "Cursor invalid request" (400) and let the client see the real verdict immediately. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(errors): let a structured server class outrank message inference Closes the reviewer blocker on #2729. Deriving response.failed status from the classified payload is only correct if httpStatusFromTerminalError respects the classification, and it did not: only server_error + server_is_overloaded was recognized, so a generic upstream_server_error fell through to message inference. An upstream 5xx whose text contained 'malformed' or 'invalid request' returned 400, and Claude Code stopped retrying a genuinely retryable failure — the same inversion #2729 set out to fix, one layer down. classifyError assigns upstream_server_error to every 5xx it observes, so the class is authoritative over the words in the message. Probed before and after against origin/dev: 429/401/403/quota/invalid-request/ client-cancel/proxy_error/undefined all unchanged, and cyber-policy refusals still return 400 because that check runs earlier. Only the generic server class moves, 400 -> 502. Regression is behavioral and mutation-verified: revert the fix and keep the test -> 45 pass / 1 fail; with the fix -> 46 pass / 0 fail. * fix(errors): narrow the server-class override to the 400 verdict only Exact-head CI caught what my focused suites missed: the first version of this fix returned a blanket 502 for any structured server class, which flattened genuine 504 and 503 verdicts and broke tests/web-search-timeout-contract.ts (a routed-body stall is really a gateway timeout, and the log surface asserts 504). Reproduced locally: 2 fail in that suite. The classification is authoritative about blame, not about which server status fits. So message inference still picks the specific status, and only the 400 verdict is overridden — the one case that both blames the caller and stops the retry. 429, 499, 401 and 403 are left alone because each is a signal the caller routes on, and overriding them would trade one misreport for another. Differential probe vs origin/dev: exactly two cases change, both 400 -> 502 (upstream 5xx carrying 'malformed' or 'invalid request' text). 504, 503, 429, 499, cyber-policy 400, auth 401, permission 403, real invalid_request 400, proxy_error 500 and the no-message 502 default are all unchanged. 210 pass / 0 fail across the nine suites that touch this function. Mutation oracle still holds: 45/1 without the fix, 46/0 with it. * fix(cursor): check failed_precondition before the overload keywords Final-round auditor found the #2729 failed_precondition arm did not fire on the shape it exists to catch. It sat AFTER the overload keywords in both classifyCursorError and inferHttpStatusFromAdapterMessage, and a plan-gated rejection normally reads 'failed_precondition: model unavailable for this plan' — which matched 'unavailable' first: classifyCursorError -> 'Cursor server overloaded' inferHttpStatus... -> 503 So clients kept retrying a deterministic rejection that can never succeed, which is the exact misclassification the arm was added to stop. The original test only covered 'Cursor Connect error failed_precondition: Error', which has no competing keyword, so 25/25 passed over the defect. Moved the check ahead of the overload keywords in both functions. The explicit gRPC status is a structured backend signal; 'unavailable' and 'temporarily' next to it are inference over free text. Differential vs origin/dev: exactly one case changes (fp + unavailable, 503 -> 400). Real overloads, auth, rate limit, timeout and invalid-request are all unchanged, and authentication still outranks both. 211 pass / 0 fail across the ten affected suites; tsc exit 0. --------- Co-authored-by: jun <jun@junui-MacBookPro.local> Co-authored-by: Cursor <cursoragent@cursor.com>
… false-abort a healthy turn (lidge-jun#2774) * docs(devlog): plan the igwanu bug-PR merge round Intake for the 13 open bug-labelled PRs. Records the merged-tree compile gate (12 clean, lidge-jun#2497 conflicting), the pairwise file-contention map, and the finding that orders the round: lidge-jun#2767, lidge-jun#2764 and lidge-jun#2747 fail required CI on a shared repository-wide assertion (package.json 2.34.0 == released tag v2.34.0), not on their own code. lidge-jun#2766 repairs it and is the keystone. * docs(devlog): keep the round plan out of its own privacy-scan finding The roadmap documented the scp-style SSH literal by quoting it, which reproduced the exact privacy:scan failure the plan says lidge-jun#2766 repairs. Describe the remote instead of quoting it; devlog/ is scanned. * docs(devlog): split the shared CI failure per job, per PR A-gate reviewer (Sol high) found the per-PR accounting collapsed two distinct shared defects into one. test 3/4 and macos fail on release-version-line; gates fails on the privacy-scan runbook literal; ci is the fan-in. lidge-jun#2747 has no gates failure at all because its head predates the runbook doc. * docs(devlog): close the two A-gate blockers on the round plan Adversarial reviewer (Sol high) returned FAIL on two counts, both correct. 1. The plan reproduced the unfixed lidge-jun#2745 OAuth credential-boundary defect, its activation sequence and its remediation inside devlog/ — a public tracked directory — while the PR is still open. AGENTS.md forbids exactly that. The analysis moves to .tmp/ (gitignored) and the tracked doc keeps only the disposition line and a pointer to the PR. 2. Every merge lane went straight from 'CI green' to 'merge', skipping the non-author maintainer approval MAINTAINERS.md requires. All four Ingwannu PRs read REVIEW_REQUIRED, and package.json is a restricted surface per .github/scripts/pr-sponsored-surface.cjs. A round-level instruction is not an exact-head approval; each lane now carries the gate explicitly, and lidge-jun#2766 exiting as NEEDS_HUMAN(approval) is recorded as a real terminal state. Also: narrowed the wp2 barrier to merges only (review and rebases can run in parallel — disjoint paths), recorded mutation-test evidence that the lidge-jun#2733, lidge-jun#2726 and lidge-jun#2761 regressions are load-bearing, and replaced the overbroad 'No auth surface' line on lidge-jun#2729 with the precise boundary. * docs(devlog): remove the last pre-disclosure shape from the round plan Re-verification caught a third instance my first repair missed: the TESTS section still named the regression design for the unfixed lidge-jun#2745 defect, which carries the activation shape even without the prose. It now points at scratch. The reviewer also found the same mechanism in two OTHER open _plan units (260826_wp7e_presence_driven_oauth_failover, 260827_dev_hardening). Those are pre-existing and predate this round; recorded as a follow-up rather than silently rewritten here. * docs(devlog): lock the igwanu round roadmap after the A gate wp1 close-out. Records the four-round adversarial audit, both accepted blockers and their repairs, the resolved approval path (lidge-jun is a valid non-author approver for Ingwannu PRs per MAINTAINERS.md:57-59), and the keystone proof: 15334 pass / 0 fail on lidge-jun#2766's merged tree via ocx-run on lidge, with the release-version test and privacy scan green there and red on plain dev. * docs(devlog): record wp2 — keystone landed and hypothesis proven dev 8b1b65b -> 50e9556, six PRs merged (lidge-jun#2766 lidge-jun#2733 lidge-jun#2726 lidge-jun#2761 lidge-jun#2764 lidge-jun#2767). The keystone claim was tested rather than assumed: lidge-jun#2764 and lidge-jun#2767 were rebased onto post-lidge-jun#2766 dev with no change to their own diffs, and both went from four failing required jobs to 26 success / 0 failures. One version string and one doc line cleared red CI across three unrelated PRs. Also records why the A-gate approval blocker mattered, and the fork-branch constraint that makes rerunning CI the correct move for lidge-jun#2747. * docs(devlog): correct the lidge-jun#2747 claim in the wp2 record Post-execution auditor (Sol high) verified all six merges — first-parent count, patch identity across the rebase, exact-head approvers, post-merge dev CI — and found I overstated one thing. I wrote that four required jobs went green across THREE PRs and that lidge-jun#2747's rerun was 'in flight'. Neither was true. gh run rerun --failed replays the same commit, so lidge-jun#2747 attempt 4 completed red on the same 2.34.0/v2.34.0 collision, and it was already terminal ~70 seconds before I committed the claim. The keystone proof rests on lidge-jun#2764 and lidge-jun#2767 only; lidge-jun#2747 is diagnosed and awaiting an author rebase because its head is on a fork. Also notes lidge-jun#2766 carries two approval events (before and after a check re-run). * docs(devlog): fix two errors introduced by the previous correction Auditor FAILed my corrections, correctly. 1. The 'operational notes' still said re-running CI was the CORRECT action for a fork PR — directly contradicting the section I had just written explaining that a rerun replays the same commit. It now says plainly that neither option was available to me and only an author rebase moves lidge-jun#2747. 2. I claimed the two lidge-jun#2766 approvals straddled a check re-run. They did not: 15:27:49Z and 15:28:04Z, 15 seconds apart, with every head workflow already complete by 14:44:38Z. Replaced with the actual timestamps. An incorrect correction is worse than the original error. * docs(devlog): lidge-jun#2747 was a choice, not a constraint Third auditor FAIL on the same paragraph, and right again. I wrote that rewriting the contributor's branch was 'not available'. GitHub reports maintainerCanModify=true on lidge-jun#2747, so it was available the whole time. What actually happened is that I chose not to force-push a rebase onto work another contributor owns when the only defect was in our base. That is a judgement about ownership and it now reads as one, including the admission that an earlier draft dressed it up as a technical limit. * docs(devlog): record the lidge-jun#2729 supersede and the blanket-502 mistake lidge-jun#2729's fix only helps if the classification actually wins, and it did not: httpStatusFromTerminalError recognized one code pair and let a generic upstream_server_error fall through to message inference, returning 400 for an upstream 5xx whose text contained 'malformed'. My first repair returned a blanket 502 and passed nine focused suites — 210/0 — while breaking two CI shards, because a web-search stall genuinely is 504 and flattening it discards information. The override narrowed to the 400 verdict alone. The transferable finding: green TARGETED checks are not health either, because you chose the targets. The differential probe against unpatched dev is what made the recovery cheap. * docs(devlog): lidge-jun#2769 is NEEDS_HUMAN — I cannot approve my own PR GitHub refuses the review outright. The Ingwannu PRs were approvable because author and approver were different maintainers; here I am both, so the MAINTAINERS.md self-approval rule binds and is platform-enforced. Every technical gate is green (15349/0 full suite, required CI green at the exact head, mutation oracle held). The missing input is a second maintainer. * docs(devlog): round outcome — 13 bug PRs dispositioned Six merged, one closed as superseded, six open each with a named unblocking condition and the person who owns it. dev 8b1b65b -> 50e9556 through PRs only. Records the two gates this round adds: red checks are not harm until the shared baseline is green, and green TARGETED checks are not health either because you chose the targets. Also records the six reviewer FAILs, including the two about honesty rather than correctness. * docs(devlog): correct three claims the final audit disproved 1. 'lidge-jun#2769: all gates green, approval is the only blocker' was false — an unresolved failed_precondition precedence defect was still open. Fixed in 16cb875 and the claim corrected rather than dropped. 2. The macOS CL-07 failure is UNATTRIBUTED, not proven flaky. Both arguments I used were wrong: the test does reach the changed code transitively, and the comparison failure on d1def68 was Linux, not macOS. 3. Behind-counts were stale by 13 commits — measured against the round's opening base rather than the dev its own merges produced. Now 131/192/399. * devlog(260828_cursor_ndjson_backlog_train): roadmap unit — backlog RCA + cursor defect inventory + phase docs * devlog(260828): fold A-gate round-1 blockers into roadmap (13 findings) * devlog(260828): roadmap lock — wp map bound to decade docs * devlog(260828): wp2 audit round-2 fold — combined-length coalescing threshold * fix(responses): coalesce buffered deltas so a stalled consumer cannot false-abort a healthy turn The runTurn backlog cap counts events, not tokens. A Codex app consumer mid-reconnect (Bun delivers the disconnect late) stopped pulling while the adapter kept streaming token-granular deltas, hit the 1024-event cap within seconds, and killed the turn with a fabricated adapter error. Adjacent same-phase text deltas, adjacent thinking deltas, and consecutive heartbeats now merge at push time when no reader is waiting (combined-length ceiling 64K code units; tail replaced, never mutated). The overflow message now names the real condition: a stalled consumer. * devlog(260828): fold wp3 runbook audit — isolated homes, no-refresh gate, direct startServer launcher, capture contract * devlog(260828): wp3 probe round 1 — N4 validates coalescing live; mid-stream envelope echo + mar corruption captured * devlog(260828): 031 diff spec — mid-stream envelope-echo detection (diagnostic-only round) * devlog(260828): strip remote home paths from 020 (privacy:scan) * test(cursor): give the discovery retry test a CI-proof timeout budget The 120ms budget applied to BOTH attempts (retry cap is min-ed with it); on a loaded runner the succeeding second attempt also timed out. 1s keeps the test deterministic without materially slowing the suite.
…payload (lidge-jun#2769) * fix(claude): derive response.failed status from the classified error payload Internal response.failed envelopes carry the classified {type, code, message} but no numeric status, and the Claude outbound defaulted every such failure to 500 — inside the transient set — so a Cursor plan/quota 429 reached Claude Code as a retryable overloaded_error and surfaced as "Repeated 529 Overloaded errors" while /api/logs correctly recorded 429. Derive the missing status with httpStatusFromTerminalError, the same mapping the request log uses, so rate-limit, auth, and invalid-request failures keep their real Anthropic error type. Unclassified synthetic tails still land on transient 5xx and map to overloaded_error as designed. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cursor): classify failed_precondition as a non-retryable invalid request A plan-gated model invoked on a plan without it (live: cursor/claude-fable-5) is rejected with "Cursor Connect error failed_precondition", which fell through to "Cursor upstream error" (502) — a transient status the Claude inbound then surfaced as a retryable 529 overloaded_error. gRPC FAILED_PRECONDITION is deterministic and non-retryable by definition, so classify it as "Cursor invalid request" (400) and let the client see the real verdict immediately. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(errors): let a structured server class outrank message inference Closes the reviewer blocker on lidge-jun#2729. Deriving response.failed status from the classified payload is only correct if httpStatusFromTerminalError respects the classification, and it did not: only server_error + server_is_overloaded was recognized, so a generic upstream_server_error fell through to message inference. An upstream 5xx whose text contained 'malformed' or 'invalid request' returned 400, and Claude Code stopped retrying a genuinely retryable failure — the same inversion lidge-jun#2729 set out to fix, one layer down. classifyError assigns upstream_server_error to every 5xx it observes, so the class is authoritative over the words in the message. Probed before and after against origin/dev: 429/401/403/quota/invalid-request/ client-cancel/proxy_error/undefined all unchanged, and cyber-policy refusals still return 400 because that check runs earlier. Only the generic server class moves, 400 -> 502. Regression is behavioral and mutation-verified: revert the fix and keep the test -> 45 pass / 1 fail; with the fix -> 46 pass / 0 fail. * fix(errors): narrow the server-class override to the 400 verdict only Exact-head CI caught what my focused suites missed: the first version of this fix returned a blanket 502 for any structured server class, which flattened genuine 504 and 503 verdicts and broke tests/web-search-timeout-contract.ts (a routed-body stall is really a gateway timeout, and the log surface asserts 504). Reproduced locally: 2 fail in that suite. The classification is authoritative about blame, not about which server status fits. So message inference still picks the specific status, and only the 400 verdict is overridden — the one case that both blames the caller and stops the retry. 429, 499, 401 and 403 are left alone because each is a signal the caller routes on, and overriding them would trade one misreport for another. Differential probe vs origin/dev: exactly two cases change, both 400 -> 502 (upstream 5xx carrying 'malformed' or 'invalid request' text). 504, 503, 429, 499, cyber-policy 400, auth 401, permission 403, real invalid_request 400, proxy_error 500 and the no-message 502 default are all unchanged. 210 pass / 0 fail across the nine suites that touch this function. Mutation oracle still holds: 45/1 without the fix, 46/0 with it. * fix(cursor): check failed_precondition before the overload keywords Final-round auditor found the lidge-jun#2729 failed_precondition arm did not fire on the shape it exists to catch. It sat AFTER the overload keywords in both classifyCursorError and inferHttpStatusFromAdapterMessage, and a plan-gated rejection normally reads 'failed_precondition: model unavailable for this plan' — which matched 'unavailable' first: classifyCursorError -> 'Cursor server overloaded' inferHttpStatus... -> 503 So clients kept retrying a deterministic rejection that can never succeed, which is the exact misclassification the arm was added to stop. The original test only covered 'Cursor Connect error failed_precondition: Error', which has no competing keyword, so 25/25 passed over the defect. Moved the check ahead of the overload keywords in both functions. The explicit gRPC status is a structured backend signal; 'unavailable' and 'temporarily' next to it are inference over free text. Differential vs origin/dev: exactly one case changes (fp + unavailable, 503 -> 400). Real overloads, auth, rate limit, timeout and invalid-request are all unchanged, and authentication still outranks both. 211 pass / 0 fail across the ten affected suites; tsc exit 0. --------- Co-authored-by: jun <jun@junui-MacBookPro.local> Co-authored-by: Cursor <cursoragent@cursor.com>
… false-abort a healthy turn (lidge-jun#2774) * docs(devlog): plan the igwanu bug-PR merge round Intake for the 13 open bug-labelled PRs. Records the merged-tree compile gate (12 clean, lidge-jun#2497 conflicting), the pairwise file-contention map, and the finding that orders the round: lidge-jun#2767, lidge-jun#2764 and lidge-jun#2747 fail required CI on a shared repository-wide assertion (package.json 2.34.0 == released tag v2.34.0), not on their own code. lidge-jun#2766 repairs it and is the keystone. * docs(devlog): keep the round plan out of its own privacy-scan finding The roadmap documented the scp-style SSH literal by quoting it, which reproduced the exact privacy:scan failure the plan says lidge-jun#2766 repairs. Describe the remote instead of quoting it; devlog/ is scanned. * docs(devlog): split the shared CI failure per job, per PR A-gate reviewer (Sol high) found the per-PR accounting collapsed two distinct shared defects into one. test 3/4 and macos fail on release-version-line; gates fails on the privacy-scan runbook literal; ci is the fan-in. lidge-jun#2747 has no gates failure at all because its head predates the runbook doc. * docs(devlog): close the two A-gate blockers on the round plan Adversarial reviewer (Sol high) returned FAIL on two counts, both correct. 1. The plan reproduced the unfixed lidge-jun#2745 OAuth credential-boundary defect, its activation sequence and its remediation inside devlog/ — a public tracked directory — while the PR is still open. AGENTS.md forbids exactly that. The analysis moves to .tmp/ (gitignored) and the tracked doc keeps only the disposition line and a pointer to the PR. 2. Every merge lane went straight from 'CI green' to 'merge', skipping the non-author maintainer approval MAINTAINERS.md requires. All four Ingwannu PRs read REVIEW_REQUIRED, and package.json is a restricted surface per .github/scripts/pr-sponsored-surface.cjs. A round-level instruction is not an exact-head approval; each lane now carries the gate explicitly, and lidge-jun#2766 exiting as NEEDS_HUMAN(approval) is recorded as a real terminal state. Also: narrowed the wp2 barrier to merges only (review and rebases can run in parallel — disjoint paths), recorded mutation-test evidence that the lidge-jun#2733, lidge-jun#2726 and lidge-jun#2761 regressions are load-bearing, and replaced the overbroad 'No auth surface' line on lidge-jun#2729 with the precise boundary. * docs(devlog): remove the last pre-disclosure shape from the round plan Re-verification caught a third instance my first repair missed: the TESTS section still named the regression design for the unfixed lidge-jun#2745 defect, which carries the activation shape even without the prose. It now points at scratch. The reviewer also found the same mechanism in two OTHER open _plan units (260826_wp7e_presence_driven_oauth_failover, 260827_dev_hardening). Those are pre-existing and predate this round; recorded as a follow-up rather than silently rewritten here. * docs(devlog): lock the igwanu round roadmap after the A gate wp1 close-out. Records the four-round adversarial audit, both accepted blockers and their repairs, the resolved approval path (lidge-jun is a valid non-author approver for Ingwannu PRs per MAINTAINERS.md:57-59), and the keystone proof: 15334 pass / 0 fail on lidge-jun#2766's merged tree via ocx-run on lidge, with the release-version test and privacy scan green there and red on plain dev. * docs(devlog): record wp2 — keystone landed and hypothesis proven dev 80df093 -> b87b7b1, six PRs merged (lidge-jun#2766 lidge-jun#2733 lidge-jun#2726 lidge-jun#2761 lidge-jun#2764 lidge-jun#2767). The keystone claim was tested rather than assumed: lidge-jun#2764 and lidge-jun#2767 were rebased onto post-lidge-jun#2766 dev with no change to their own diffs, and both went from four failing required jobs to 26 success / 0 failures. One version string and one doc line cleared red CI across three unrelated PRs. Also records why the A-gate approval blocker mattered, and the fork-branch constraint that makes rerunning CI the correct move for lidge-jun#2747. * docs(devlog): correct the lidge-jun#2747 claim in the wp2 record Post-execution auditor (Sol high) verified all six merges — first-parent count, patch identity across the rebase, exact-head approvers, post-merge dev CI — and found I overstated one thing. I wrote that four required jobs went green across THREE PRs and that lidge-jun#2747's rerun was 'in flight'. Neither was true. gh run rerun --failed replays the same commit, so lidge-jun#2747 attempt 4 completed red on the same 2.34.0/v2.34.0 collision, and it was already terminal ~70 seconds before I committed the claim. The keystone proof rests on lidge-jun#2764 and lidge-jun#2767 only; lidge-jun#2747 is diagnosed and awaiting an author rebase because its head is on a fork. Also notes lidge-jun#2766 carries two approval events (before and after a check re-run). * docs(devlog): fix two errors introduced by the previous correction Auditor FAILed my corrections, correctly. 1. The 'operational notes' still said re-running CI was the CORRECT action for a fork PR — directly contradicting the section I had just written explaining that a rerun replays the same commit. It now says plainly that neither option was available to me and only an author rebase moves lidge-jun#2747. 2. I claimed the two lidge-jun#2766 approvals straddled a check re-run. They did not: 15:27:49Z and 15:28:04Z, 15 seconds apart, with every head workflow already complete by 14:44:38Z. Replaced with the actual timestamps. An incorrect correction is worse than the original error. * docs(devlog): lidge-jun#2747 was a choice, not a constraint Third auditor FAIL on the same paragraph, and right again. I wrote that rewriting the contributor's branch was 'not available'. GitHub reports maintainerCanModify=true on lidge-jun#2747, so it was available the whole time. What actually happened is that I chose not to force-push a rebase onto work another contributor owns when the only defect was in our base. That is a judgement about ownership and it now reads as one, including the admission that an earlier draft dressed it up as a technical limit. * docs(devlog): record the lidge-jun#2729 supersede and the blanket-502 mistake lidge-jun#2729's fix only helps if the classification actually wins, and it did not: httpStatusFromTerminalError recognized one code pair and let a generic upstream_server_error fall through to message inference, returning 400 for an upstream 5xx whose text contained 'malformed'. My first repair returned a blanket 502 and passed nine focused suites — 210/0 — while breaking two CI shards, because a web-search stall genuinely is 504 and flattening it discards information. The override narrowed to the 400 verdict alone. The transferable finding: green TARGETED checks are not health either, because you chose the targets. The differential probe against unpatched dev is what made the recovery cheap. * docs(devlog): lidge-jun#2769 is NEEDS_HUMAN — I cannot approve my own PR GitHub refuses the review outright. The Ingwannu PRs were approvable because author and approver were different maintainers; here I am both, so the MAINTAINERS.md self-approval rule binds and is platform-enforced. Every technical gate is green (15349/0 full suite, required CI green at the exact head, mutation oracle held). The missing input is a second maintainer. * docs(devlog): round outcome — 13 bug PRs dispositioned Six merged, one closed as superseded, six open each with a named unblocking condition and the person who owns it. dev 80df093 -> b87b7b1 through PRs only. Records the two gates this round adds: red checks are not harm until the shared baseline is green, and green TARGETED checks are not health either because you chose the targets. Also records the six reviewer FAILs, including the two about honesty rather than correctness. * docs(devlog): correct three claims the final audit disproved 1. 'lidge-jun#2769: all gates green, approval is the only blocker' was false — an unresolved failed_precondition precedence defect was still open. Fixed in 16cb875 and the claim corrected rather than dropped. 2. The macOS CL-07 failure is UNATTRIBUTED, not proven flaky. Both arguments I used were wrong: the test does reach the changed code transitively, and the comparison failure on 3b8e703 was Linux, not macOS. 3. Behind-counts were stale by 13 commits — measured against the round's opening base rather than the dev its own merges produced. Now 131/192/399. * devlog(260828_cursor_ndjson_backlog_train): roadmap unit — backlog RCA + cursor defect inventory + phase docs * devlog(260828): fold A-gate round-1 blockers into roadmap (13 findings) * devlog(260828): roadmap lock — wp map bound to decade docs * devlog(260828): wp2 audit round-2 fold — combined-length coalescing threshold * fix(responses): coalesce buffered deltas so a stalled consumer cannot false-abort a healthy turn The runTurn backlog cap counts events, not tokens. A Codex app consumer mid-reconnect (Bun delivers the disconnect late) stopped pulling while the adapter kept streaming token-granular deltas, hit the 1024-event cap within seconds, and killed the turn with a fabricated adapter error. Adjacent same-phase text deltas, adjacent thinking deltas, and consecutive heartbeats now merge at push time when no reader is waiting (combined-length ceiling 64K code units; tail replaced, never mutated). The overflow message now names the real condition: a stalled consumer. * devlog(260828): fold wp3 runbook audit — isolated homes, no-refresh gate, direct startServer launcher, capture contract * devlog(260828): wp3 probe round 1 — N4 validates coalescing live; mid-stream envelope echo + mar corruption captured * devlog(260828): 031 diff spec — mid-stream envelope-echo detection (diagnostic-only round) * devlog(260828): strip remote home paths from 020 (privacy:scan) * test(cursor): give the discovery retry test a CI-proof timeout budget The 120ms budget applied to BOTH attempts (retry cap is min-ed with it); on a loaded runner the succeeding second attempt also timed out. 1s keeps the test deterministic without materially slowing the suite.
…payload (lidge-jun#2769) * fix(claude): derive response.failed status from the classified error payload Internal response.failed envelopes carry the classified {type, code, message} but no numeric status, and the Claude outbound defaulted every such failure to 500 — inside the transient set — so a Cursor plan/quota 429 reached Claude Code as a retryable overloaded_error and surfaced as "Repeated 529 Overloaded errors" while /api/logs correctly recorded 429. Derive the missing status with httpStatusFromTerminalError, the same mapping the request log uses, so rate-limit, auth, and invalid-request failures keep their real Anthropic error type. Unclassified synthetic tails still land on transient 5xx and map to overloaded_error as designed. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cursor): classify failed_precondition as a non-retryable invalid request A plan-gated model invoked on a plan without it (live: cursor/claude-fable-5) is rejected with "Cursor Connect error failed_precondition", which fell through to "Cursor upstream error" (502) — a transient status the Claude inbound then surfaced as a retryable 529 overloaded_error. gRPC FAILED_PRECONDITION is deterministic and non-retryable by definition, so classify it as "Cursor invalid request" (400) and let the client see the real verdict immediately. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(errors): let a structured server class outrank message inference Closes the reviewer blocker on lidge-jun#2729. Deriving response.failed status from the classified payload is only correct if httpStatusFromTerminalError respects the classification, and it did not: only server_error + server_is_overloaded was recognized, so a generic upstream_server_error fell through to message inference. An upstream 5xx whose text contained 'malformed' or 'invalid request' returned 400, and Claude Code stopped retrying a genuinely retryable failure — the same inversion lidge-jun#2729 set out to fix, one layer down. classifyError assigns upstream_server_error to every 5xx it observes, so the class is authoritative over the words in the message. Probed before and after against origin/dev: 429/401/403/quota/invalid-request/ client-cancel/proxy_error/undefined all unchanged, and cyber-policy refusals still return 400 because that check runs earlier. Only the generic server class moves, 400 -> 502. Regression is behavioral and mutation-verified: revert the fix and keep the test -> 45 pass / 1 fail; with the fix -> 46 pass / 0 fail. * fix(errors): narrow the server-class override to the 400 verdict only Exact-head CI caught what my focused suites missed: the first version of this fix returned a blanket 502 for any structured server class, which flattened genuine 504 and 503 verdicts and broke tests/web-search-timeout-contract.ts (a routed-body stall is really a gateway timeout, and the log surface asserts 504). Reproduced locally: 2 fail in that suite. The classification is authoritative about blame, not about which server status fits. So message inference still picks the specific status, and only the 400 verdict is overridden — the one case that both blames the caller and stops the retry. 429, 499, 401 and 403 are left alone because each is a signal the caller routes on, and overriding them would trade one misreport for another. Differential probe vs origin/dev: exactly two cases change, both 400 -> 502 (upstream 5xx carrying 'malformed' or 'invalid request' text). 504, 503, 429, 499, cyber-policy 400, auth 401, permission 403, real invalid_request 400, proxy_error 500 and the no-message 502 default are all unchanged. 210 pass / 0 fail across the nine suites that touch this function. Mutation oracle still holds: 45/1 without the fix, 46/0 with it. * fix(cursor): check failed_precondition before the overload keywords Final-round auditor found the lidge-jun#2729 failed_precondition arm did not fire on the shape it exists to catch. It sat AFTER the overload keywords in both classifyCursorError and inferHttpStatusFromAdapterMessage, and a plan-gated rejection normally reads 'failed_precondition: model unavailable for this plan' — which matched 'unavailable' first: classifyCursorError -> 'Cursor server overloaded' inferHttpStatus... -> 503 So clients kept retrying a deterministic rejection that can never succeed, which is the exact misclassification the arm was added to stop. The original test only covered 'Cursor Connect error failed_precondition: Error', which has no competing keyword, so 25/25 passed over the defect. Moved the check ahead of the overload keywords in both functions. The explicit gRPC status is a structured backend signal; 'unavailable' and 'temporarily' next to it are inference over free text. Differential vs origin/dev: exactly one case changes (fp + unavailable, 503 -> 400). Real overloads, auth, rate limit, timeout and invalid-request are all unchanged, and authentication still outranks both. 211 pass / 0 fail across the ten affected suites; tsc exit 0. --------- Co-authored-by: jun <jun@junui-MacBookPro.local> Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
API Error: Repeated 529 Overloaded errorswhen the real upstream failure was something else — observed live withcursor/claude-fable-5, where Cursor rejected the Run and/api/logsrecorded the real status (429, later 502-class) while the client was told 529 overloaded.response.failedenvelopes carry the classified{type, code, message}(e.g.rate_limit_error/rate_limit_exceeded) but never a numericstatus(adapterFailureFromEventdropshttpStatusat the wire boundary). The Claude outbound insrc/claude/outbound.tsonly honored a numericerror.statusand defaulted everything else to 500, which sits in the transient set, so every classified adapter failure was promoted tooverloaded_errorand Claude Code retried it as if the API were at capacity. The outbound now derives a missing status withhttpStatusFromTerminalError— the exact mapping/api/logsalready uses — so the client signal and the request log agree by construction: 429 →rate_limit_error, 401 →authentication_error, 400 →invalid_request_error, while unclassified synthetic tails (mid-stream resets) still land on transient 5xx and map tooverloaded_erroras designed.cursor/claude-fable-5→Cursor Connect error failed_precondition) fell through toCursor upstream error(502), a transient status that clients retried as overload. gRPCFAILED_PRECONDITIONis deterministic and non-retryable by definition, soclassifyCursorErrornow maps it toCursor invalid request(400).Verification
bun test tests/claude-outbound.test.ts— 45 pass, including 4 new regression tests: classifiedrate_limit_error/authentication_error/invalid_request_errorpayloads without a numeric status keep their real Anthropic error type, and classified server-overload plus the status-less synthetic tail still map tooverloaded_error.bun test tests/cursor-errors.test.ts— new regression test:failed_preconditionclassifies asCursor invalid requestand infers HTTP 400.bun test tests/claude-529-mapping.test.ts tests/claude-messages-endpoint.test.ts tests/error-fidelity.test.ts tests/cyber-policy-error-fidelity.test.ts tests/sse-failed-tail.test.ts tests/cl01-claude-outbound-review-regressions.test.ts— 87 pass, 0 fail.bun run typecheck— clean.bun run test— the claude/cursor error-mapping suites all pass; the run also shows failures in unrelated files (tests/server-auth.test.ts,tests/api-key-attribution.test.ts,tests/lab-*.test.ts,tests/loopback-listener-integration.test.ts, …) which reproduce identically on a cleandevcheckout without this change (verified viagit stash), i.e. pre-existing local-environment failures, not regressions from this PR./api/logsshowedcursor/claude-fable-5→ 429failedandcursor/claude-4.6-opus→ 429/502 while Claude Code displayed repeated 529s; after the account change the same model fails withfailed_preconditionwhilecursor/claude-sonnet-5,cursor/claude-4.6-sonnet, andcursor/kimi-k3-1msucceed through the identical path.Checklist
Summary by CodeRabbit
Bug Fixes
Tests