Skip to content

fix: EPIPE crash on stdin.write and add lenient JSON parsing for Claude output#17

Open
madhavikodale wants to merge 2 commits into
OpenScanAI:masterfrom
madhavikodale:fix/epipe-lenient-json
Open

fix: EPIPE crash on stdin.write and add lenient JSON parsing for Claude output#17
madhavikodale wants to merge 2 commits into
OpenScanAI:masterfrom
madhavikodale:fix/epipe-lenient-json

Conversation

@madhavikodale

@madhavikodale madhavikodale commented May 25, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip orchestrates AI agents for zero-human companies
  • The claude-local adapter spawns child processes and communicates via stdin/stdout
  • When a child process exits quickly (e.g. auth errors, transient failures), its stdin stream may close before the parent writes to it
  • Node.js emits EPIPE as an asynchronous 'error' event on writable streams, not as a thrown exception
  • The previous try/catch around stdin.write() could not catch this, causing the entire Paperclip server to crash with an unhandled EPIPE
  • Additionally, Claude occasionally outputs JSON wrapped in markdown code blocks or mixed with progress text, which the strict line-by-line parser rejects
  • This PR fixes the EPIPE crash by adding an error listener on stdin and using callback-style write, and adds lenient JSON parsing to handle non-standard Claude output
  • The benefit is a more stable server that survives child-process edge cases and correctly interprets Claude responses

What Changed

  • Added parseJsonLenient() utility in adapter-utils/src/server-utils.ts that attempts direct parse, then code-block extraction, then progressive substring parsing
  • Added extractResultFromMixedOutput() in claude-local/src/server/parse.ts to find JSON objects with "type": "result" inside mixed text
  • Updated parseClaudeStreamJson() to use lenient parsing as fallback when line-by-line parsing finds no result
  • Fixed EPIPE crash in runChildProcess(): replaced try/catch with stdin.on("error", ...) listener and callback-style stdin.write(chunk, cb)
  • Updated claude-local execute logic to treat exit-code-0 + parse failure as inferred success rather than error
  • Added comprehensive tests for parseJsonLenient() (9 cases), parseClaudeStreamJson() (8 cases), and EPIPE handling (2 cases)
  • Updated CHANGELOGs for both adapter-utils and claude-local
  • References: Fix agent runtime parsing failures and restore automatic workflow execution #16

Verification

pnpm -r typecheck  # passes
pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts packages/adapters/claude-local/src/server/parse.test.ts  # 61/61 tests pass

Manual verification:

  1. Start server with pnpm dev
  2. Trigger a claude-local run that exits quickly (e.g. auth error or empty prompt)
  3. Confirm server does not crash with EPIPE
  4. Verify Claude output with markdown-wrapped JSON is correctly parsed

Risks

  • Low risk for EPIPE fix: only changes error handling path, normal operation unchanged
  • Lenient parsing could theoretically accept malformed JSON in edge cases, but it only activates when strict parsing fails
  • The inferredSuccess flag may mask genuine failures if Claude exits 0 with garbage output; monitored via resultJson.inferredSuccess

Model Used

  • Kimi K2.5 (Moonshot AI, 256k context, tool use enabled)
  • Assisted with debugging EPIPE behavior and implementing lenient parsing strategies

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • If this change affects the UI, I have included before/after screenshots (N/A — no UI changes)
  • I have updated relevant documentation to reflect my changes
  • I have considered and documented any risks above
  • I will address all Greptile and reviewer comments before requesting merge

…de output

- Add parseJsonLenient() utility for extracting JSON from mixed text/code blocks
- Add extractResultFromMixedOutput() to find result objects in Claude output
- Fix EPIPE crash by using stdin.on('error') + callback-style write()
- Treat exit-code-0 + parse failure as inferred success in claude-local

Fixes OpenScanAI#16
…rsing

- Add parseJsonLenient() tests in adapter-utils (9 cases)
- Add parseClaudeStreamJson() tests in claude-local (8 cases)
- Add runChildProcess EPIPE handling tests (2 cases)
- Update CHANGELOGs for adapter-utils and claude-local
- All 61 targeted tests pass; pnpm -r typecheck clean
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant