Skip to content

fix(proxy): missing proxyRes error handler + SSE corruption on error after headers - #18

Draft
aattaran wants to merge 1 commit into
mainfrom
claude/fix-audit-shared-bugs-0vfeG
Draft

fix(proxy): missing proxyRes error handler + SSE corruption on error after headers#18
aattaran wants to merge 1 commit into
mainfrom
claude/fix-audit-shared-bugs-0vfeG

Conversation

@aattaran

@aattaran aattaran commented May 7, 2026

Copy link
Copy Markdown
Owner

Summary

Two production bugs in proxy/model-proxy.js surfaced by an audit run against aattaran/Jarvis2 (shared proxy ancestry — both fixes apply verbatim).

Bug 1 — missing proxyRes error handler (process crash)

The three response paths inside the httpsRequest callback —

  • proxyRes.pipe(norm).pipe(clientRes) (SSE)
  • the data/end JSON collector
  • the passthrough proxyRes.pipe(clientRes)

— never attach an 'error' handler to proxyRes. If the upstream connection drops mid-response (TCP RST, TLS abort, server crash), Node emits an unhandled 'error' on the response stream and the entire proxy process exits, killing every concurrent in-flight Claude Code session.

Fix: attach a single proxyRes.on('error', ...) immediately after the response is opened, before any of the branch-specific piping. Mirrors the existing proxyReq.on('error') shape: 502+JSON if headers haven't gone out, otherwise destroy the response.

Bug 2 — SSE corruption when proxyReq errors after headers sent

Current proxyReq.on('error') unconditionally calls:

clientRes.end(JSON.stringify({ error: { message: 'Upstream connection error' } }));

If headers already went out as text/event-stream (the common SSE case for /v1/messages), that JSON gets injected mid-stream. Claude Code's SSE parser then sees a non-event payload and either silently truncates the assistant turn or throws — depending on where in the stream the upstream died.

Fix: clientRes.destroy(err) once clientRes.headersSent is true, so the client sees a clean transport-level abort instead of a corrupted event stream.

Files

  • proxy/model-proxy.js — added proxyRes error handler; gated proxyReq error handler's end() on !headersSent.

Note on the third audit finding

The same audit also flagged the /_proxy/mode Origin-prefix CSRF/DNS-rebinding issue. That's already covered by PR #16 (open, from aaronjmars), so it is intentionally not in this PR.

Test plan

  • Local repro: kill upstream mid-SSE (iptables -A OUTPUT -p tcp --dport 443 -j REJECT after first chunk) → before patch: process crashes / SSE garbage; after patch: clean client error, proxy survives.
  • Existing happy path unchanged: streaming + JSON /v1/messages still flow through UsageNormalizer and produce correct /_proxy/cost.
  • /_proxy/status, /_proxy/mode, /_proxy/cost still respond correctly.

Generated by Claude Code

g-roliveira added a commit to g-roliveira/deepclaude that referenced this pull request Aug 6, 2026
…parity

Summary of fixes and features applied from aattaran/deepclaude
issues and PRs, plus original improvements:

Model coverage (Issue aattaran#39):
- Add claude-fable-5, claude-opus-5, claude-sonnet-5 to MODEL_REMAP
- Tier-based _default fallback (fable/opus/sonnet/haiku) so new
  Claude models degrade predictably instead of silently routing
  to the wrong backend
- Warning log when model is forwarded unmapped

Proxy resilience (PR aattaran#18):
- proxyRes error handler — upstream TCP reset mid-response no
  longer crashes the entire proxy process
- proxyReq error after headers-sent: destroy response instead of
  injecting JSON into the SSE stream (which corrupted the parser)

Thinking-block continuity (PR aattaran#24):
- Drop top-level thinking/context_management on non-Anthropic
  routes instead of stripping all thinking blocks from history
- Fixes DeepSeek 400: "content[].thinking must be passed back"

Proxy in normal mode (PR aattaran#9):
- Default launch now starts the proxy; cost tracking and live
  /switch work in all sessions, not just --remote
- Use ANTHROPIC_API_KEY instead of ANTHROPIC_AUTH_TOKEN so
  subscription OAuth tokens are handled correctly

Body size limit (PR aattaran#7):
- 50 MB cap on /v1/messages request body, returns 413

Forward unknown args to claude (Issue aattaran#25):
- Bash: -- separator support for explicit passthrough
- PowerShell: ValueFromRemainingArguments captures unknown flags

PowerShell parity (PR aattaran#5, aattaran#8):
- --switch/-s parameter with backend name normalization
- try/finally ensures proxy cleanup on Ctrl+C/crash

Model [1m] suffix:
- All model names include [1m] token window hint so Claude Code
  doesn't assume 200k context and auto-compact prematurely

Project documentation:
- CLAUDE.md with architecture overview, proxy routing, SSE
  normalization, model remapping, and development conventions
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