fix(agents): surface Claude CLI exit errors - #190
Merged
kunchenguid merged 4 commits intoAug 9, 2026
Merged
Conversation
added 4 commits
August 9, 2026 00:18
The claude adapter built its failure detail from stderr alone, so a CLI that reports its error on stdout (plain text or a structured JSON result event) degraded to a bare `claude exited with code 1: ` with nothing after the colon. Permanent-error classification read the same stderr-only string, so a low credit balance reported on stdout burned retries. Capture a bounded stdout tail, extract the CLI's own error text from it when the output is JSON, classify permanence against the combined detail, and say the CLI produced no output when both streams are empty. Closes kunchenguid#157
This was referenced Aug 9, 2026
Closed
Owner
|
Thanks @jasonqlwilliams-alt - merged! Really appreciate the contribution. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Fix issue #157 in the public repo kunchenguid/gnhf: the claude agent adapter swallows the agent's real error on a non-zero exit and reports an empty
claude exited with code 1:with nothing after the colon. Users cannot tell an authentication failure from a bad model name from a rate limit, which is especially bad for an overnight tool where this is the only trace left behind in the morning.Confirmed cause (verified end to end, not assumed): src/core/agents/claude.ts never captured stdout at all - parseJSONLStream silently drops unparseable lines, and the child 'close' handler built its reported detail from the accumulated stderr alone. isPermanentClaudeError read the same stderr-only string, so a permanent error reported on stdout was misclassified as retryable and burned retries. When the claude CLI reports its real failure on stdout (plain text, or a structured JSON result event), stderr is empty and the message degrades to the bare prefix.
Required changes:
Acceptance criteria:
Constraint from the user: gnhf is a public open-source project with active outside contributors, so keep the change tight and reviewable, follow the surrounding code's existing style, and do not reformat or refactor files that are not being fixed.
Work done and tradeoffs made:
and produced no output' instead of a trailing bare colon. The stderr-only wording is deliberately unchanged so existing behaviour and tests for that path are preserved.A PRIOR no-mistakes review run already raised three ask-user findings on this branch. They were escalated and the decision came back to FIX all three, and those fixes are ALREADY APPLIED in the second commit on this branch ('no-mistakes(review): narrow claude permanent-error scope, bound raw tail, add e2e'). These are accepted, deliberate decisions - do not re-litigate them:
Gates run locally on the first commit before it was committed: pnpm run typecheck, pnpm run lint, and pnpm test (673 tests across 42 files) all green.
What Changed
Risk Assessment
✅ Low: The change is tightly scoped to the Claude adapter, preserves the success path, bounds surfaced stdout, safely limits permanent-error classification to CLI-controlled text, and adds behavior-level unit and end-to-end coverage for the required failure modes.
Testing
The author-reported prior full-suite, lint, and typecheck baselines were not repeated in this targeted phase. I ran the complete Claude adapter unit file, built the CLI, ran the focused executable e2e regression, and manually exercised three end-user CLI runs covering stdout, stderr, and empty output. All checks passed, the persisted morning-after notes contain the expected useful messages, and the worktree was left clean.
Evidence: Structured stdout error persisted in notes.md
Evidence: Stderr error persisted in notes.md
Evidence: Empty-stream fallback persisted in notes.md
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
Inspectedgit diff 2c9f3bfcb519e99731a15ce8df7460fbfc904fb6..050d4aa29b6e732fe245650a0d6e9a4064a009f8for scope and acceptance coverage.pnpm exec vitest run src/core/agents/claude.test.tspnpm run buildpnpm exec vitest run e2e/e2e.test.ts -t "surfaces the claude CLI's own failure text"Ran three real built-CLI sessions against temporary git repositories with the PATH-based Claude stub usingGNHF_MOCK_CLAUDE_MODE=stdout-error,stderr-error, andno-output; inspected and preserved each generatednotes.md.git status --short --ignoredandgit diff --checkafter removing generated worktree artifacts.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.