Fresh project sessions rapidly accumulate hidden tool-result context and then fail with ECONNRESET
Preflight Checklist
- [x] I have searched existing issues for similar reports.
- [x] This is a single bug report.
- [x] I am using the latest version of Claude Code available to me.
- [x] This report does not contain private source code or secrets.
What's Wrong?
Claude Code project sessions can become unstable even when started as a fresh session. In my case, a fresh project session quickly accumulated a large hidden context/tool-result footprint and then began failing with ECONNRESET, while a minimal non-persistent Claude Code request from the same terminal/account/network succeeded immediately.
This made the failure look like a VPN/proxy/network problem, but the evidence points to Claude Code session/context handling.
Observed environment:
- Claude Code version:
2.1.177 (Claude Code) - Model: Sonnet 4.6
- OS: macOS
- Shell/terminal: zsh / Terminal.app
- Project type: frontend project with large CSS/Astro files
Minimal request succeeds from the same machine/account/proxy:
claude -p 'Reply OK' --model sonnet --effort low --no-session-persistence --output-format json
Result:
success, result: OK
duration_api_ms: ~3008
ttft_ms: ~2677
But fresh interactive project sessions fail after normal coding/tool use.
Examples from recent fresh session JSONL files:
~/.claude/projects/.../a10d6770-*.jsonl
size: 1.9MB
entries: hundreds
largest persisted user/tool-result line: ~700KB
cache_read_input_tokens later in the session: ~48k-50k
~/.claude/projects/.../ecbe50f6-*.jsonl
size: 268KB
largest persisted user/tool-result line: ~68KB
cache_read_input_tokens later in the session: ~56k-57k
~/.claude/projects/.../e0a8ebf2-*.jsonl
size: 307KB
largest persisted user/tool-result line: ~107KB
cache_read_input_tokens later in the session: ~65k-66k
Tool activity that preceded the failures included normal project operations such as:
Read src/styles/global.css
Read src/pages/index.astro
Read src/pages/theater.astro
Bash git diff --stat
Bash grep/rg style searches
The project has a large stylesheet:
src/styles/global.css: ~6,465 lines, ~149KB
src/pages/index.astro: ~1,040 lines, ~69KB
Even when I explicitly started a new project session, used /clear, and instructed Claude not to rely on old context, the session still became large quickly and hit the same failure mode.
The visible UI made this confusing:
- The user only sent a small number of prompts.
- The context/token indicator did not make it obvious that large hidden tool results were being retained.
/compactalso got stuck at 95% for ~20 minutes in a related session.- A fresh minimal non-persistent CLI request kept succeeding, so the base API/network path was healthy.
What Should Happen?
Fresh project sessions should not become unstable after ordinary tool use on moderately large source files. Claude Code should make the hidden context/tool-result footprint visible and should protect the user before sending oversized or fragile requests.
Expected behavior:
- A fresh session should not carry hidden state from prior sessions unless explicitly resumed.
/clearshould either remove enough hidden context/tool results to make the next turn safe, or clearly say what is still retained.- Large
Read/tool results should be summarized, capped, or excluded from future context by default unless the user explicitly opts in. - The context indicator should account for persisted tool results and cache-read footprint, not just the visible conversation.
- If requests repeatedly fail with
ECONNRESETafter context growth, Claude Code should surface a context/session warning instead of retrying the same oversized request. /compactshould not hang indefinitely near completion; if compaction cannot complete, it should fail with a clear recovery path.
Steps to Reproduce
This is not perfectly deterministic, but the pattern is reproducible on a frontend project with large CSS/Astro files:
- Start Claude Code in a project with a large stylesheet and page file:
``bash``
cd /path/to/frontend-project
claude --model sonnet --effort medium --no-chrome
- Ask Claude to inspect current changes and continue UI work.
- Claude runs normal tools such as
git status,git diff --stat,Read src/styles/global.css, andRead src/pages/index.astro. - After several tool calls, the session JSONL grows quickly, including large persisted tool-result lines.
- Follow-up turns begin hanging/retrying or fail with:
``textverbose: true
API Error: Unable to connect to API (ECONNRESET)
The socket connection was closed unexpectedly. For more information, pass in the second argument to fetch()``
- Run a minimal non-persistent request from the same terminal/account/network:
``bash``
claude -p 'Reply OK' --model sonnet --effort low --no-session-persistence --output-format json
- The minimal request succeeds quickly, suggesting the failure is tied to the project session/context rather than a general network outage.
Related Issues
Related but not identical:
- #68231: large
Readtool result injected into context, causing repeatedECONNRESET - #5674: persistent
ECONNRESETreports - #45224: SSE keep-alive/heartbeat during long tool execution
- #58554: JSONL/session-chain issues around
ECONNRESET
This report is broader than a single oversized Read: even fresh project sessions can quickly accumulate large hidden context/tool-result state, making normal follow-up turns fragile while minimal non-persistent requests continue to work.
Error Messages/Logs
API Error: Unable to connect to API (ECONNRESET)
The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()
Related compact behavior:
Compacting conversation... stuck around 95% for ~20 minutes
Additional Information
From a user perspective, this is especially costly because the symptom looks like a network/VPN/proxy issue. In this case I spent significant time debugging network settings before isolating that:
- minimal
--no-session-persistencerequests work; - failures correlate with project session context growth;
- large tool results are persisted in JSONL;
- the visible message count is much smaller than the actual hidden session payload.
Please consider adding guardrails around large tool results, clearer context accounting, and a recovery hint when ECONNRESET correlates with oversized session context.
Showing cached comments. Read the full discussion on GitHub ↗
6 Comments
Found 1 possible duplicate issue:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Your diagnosis is unusually thorough — pinpointing the ~700KB persisted
tool_resultline is the key, and I think it points somewhere slightly different from a context-window problem. Sharing a few things from poking at my own session JSONLs, plus a way to rescue the stuck session.cache_read_input_tokensstaying at ~48–66k means the token footprint is actually small — that's nowhere near a context limit. But the request body is large: Claude Code rebuilds the whole conversation and re-sends it on every turn, so once a 700KBtool_resultis persisted, every subsequent request carries that 700KB+ in its body.ECONNRESETis the socket being closed mid-request, which is much more consistent with an oversized/slow upload than with a token limit. Your own control proves it: the--no-session-persistenceminimal request (tiny body) succeeds on the same machine/network every time.Falsifiable check: start fresh and deliberately don't let it
Readthe big files (see below). IfECONNRESETdisappears, it's body-size, not network and not the context window./clearresets the conversation, but if the next turn re-Readsglobal.css(~149KB) andindex.astro(~69KB), the largetool_resultlines come right back within the same session. The growth you saw isn't leaked state from prior sessions — fresh sessions get their own<session-id>.jsonland don't inherit old turns unless you--resume. It's re-reading the big files inside the new session./compacthas to send the entire conversation to the model to summarize it. If the conversation already contains the oversized request thatECONNRESETs, compaction's own call hits the same wall — which lines up with it hanging at 95%.The bloat is whole-file
Reads of large source files. Until tool results are capped by default, you can avoid it:Grep/rgto locate the relevant region, thenReadwithoffset/limitinstead of reading the whole 149KB stylesheet.global.cssis ~37k tokens of body weight that then rides along on every later turn.The session is on disk at
~/.claude/projects/<project>/<session-id>.jsonl, one JSON record per line, and you already know the bloated lines. You can shrink the gianttool_resultcontent in place and resume. The one trap: don't delete the whole line — atool_resultis auserrecord carrying atool_use_idthat pairs with the preceding assistanttool_use; remove it and the next request becomes invalid (tool_usewith no result → 400). Keep the record and itstool_use_id, just replace the huge content with a stub.This script does that (back up the file first):
Then move the
.fixed.jsonlover the original (keep your backup) andclaude --resume <session-id>. I tested the transform on synthetic records with bothcontentshapes (array and string): every output line stays valid JSON and bothtool_use_ids are preserved, 2.1MB → 0.6KB. The history reads as "tool output was truncated" but the session continues.This is a workaround, not a fix — the real fixes are the ones you listed (cap/summarize large
tool_results by default, and make the persisted footprint visible in the context indicator), which only Anthropic can do.Your diagnosis is right: the session instability is driven by those persisted tool-result payloads in the JSONL, not conversation length. When CC does
Read global.css(149KB), that full content lands in the JSONL and stays there — every subsequent API call re-sends it as context. With a few of those reads in a session the JSONL balloons fast, and the repeat ECONNRESET is the API choking on an oversized request body, not a network problem.The
/compacthang-at-95% is a related symptom: compaction runs via the API too, so if the context is already borderline on size, the compaction request itself times out or fails.A tool called cozempic addresses this directly.
cozempic current --diagnosewill show you a breakdown of what's inflating the JSONL (tool-result payloads are usually the culprit) and how much of the context window they're occupying.cozempic treat currentthen prunes the oversized tool-result content from those sessions using a tool-output-trim strategy — keeping structure intact but removing the multi-hundred-KB payloads that are re-sent on every turn. Running it on your 1.9MB file should bring it to something the API can actually handle.For prevention: the guard daemon (
cozempic guard) monitors active sessions and auto-prunes before they hit the threshold that triggers the ECONNRESET loop. It terminates Claude first (so it's never a second writer to the file), prunes, then resumes — the runaway growth stays bounded without manual intervention.What cozempic doesn't fix: CC reading and persisting large files in the first place. That's an Anthropic-side decision about what belongs in context. But the tool-result contents that are already in your JSONL can be reclaimed today, before the next session.
If you run
--diagnoseand the breakdown looks unexpected, worth sharing here — useful data for the Anthropic report.Exactly — and your
/compact-hangs-at-95% point nails the second-order effect: compaction is itself an API round-trip that has to send the oversized body first, so the very thing you're trying to shrink is what makes the shrink request time out. That's why it stalls right at the end rather than failing fast.cozempicis a good post-hoc fix (prune the JSONL after it's already bloated). The complementary move is preventive — stop the 149KBReadfrom landing in the transcript in the first place. APreToolUseguard onReadthat blocks a wholesale read of a large file (unless it's already scoped withoffset/limit) keeps the gianttool_resultout of the JSONL entirely:I smoke-tested it: blocks a 300KB wholesale
Read, but allows the same file when called withoffset/limit, and allows small files untouched — so it nudges the agent toward scoped reads without getting in the way. Pair it withcozempicand you get both halves: prevention + cleanup.The real fix is provider-side (cap how much of a single
tool_resultis re-serialized into each request, or summarize large reads after first use), but until then keeping big reads out of the transcript is the cheapest way to stop the body from growing towardECONNRESET.@yurukusa that PreToolUse hook is the most practical prevention I've seen for this specific problem. The 256KB ceiling with scoped-read pass-through is the right balance — nudges the agent toward chunked reads without blocking legitimate whole-file operations.
The prevention + cleanup framing is exactly right. Guard handles what slips through: files the agent decides are necessary regardless, pre-existing JSONL bloat before you add the hook, or any escape path the hook can't intercept. Running them together you get both halves — hook blocks new accumulation, guard keeps the existing file lean.
The real provider-side fix (cap how much of a single
tool_resultgets re-serialized into each request, or summarize large reads after first use) is the right long-term target. Until then this combination gets you most of the way there.The hidden-accumulation framing is spot on — it's the persisted tool-result footprint, not the visible message count, that grows. For the command side of that (the
git status,git diff --stat,rgcalls here), one lever is returning structured/stripped output instead of raw terminal text: I wrap those as MCP servers in Pare that emit compact schema-validated JSON (~85% fewer tokens), so eachgit/grepresult adds far less to the footprint that piles up over a session. Doesn't help the largeReadresults — but it shrinks the command-output half.