Repository navigation
ECA becomes unresponsive when tool output causes LLM context overflow #391
Description
Description
When a tool (especially shell commands) produces very large output, ECA hits the LLM provider's token limit with:
LLM response status: 400 body: {"error":{"message":"prompt token count of 174837 exceeds the limit of 168000","code":"model_max_prompt_tokens_exceeded"}}
After this error, the ECA server and Emacs become unresponsive/frozen, requiring a restart.
Root Cause Analysis
The issue involves multiple contributing factors on both server and client side.
Server-side
-
No proactive token budget check before API call: The server sends the full message history to the LLM provider without estimating token usage. Context overflow is only detected reactively (HTTP 400 response) and recovered via prune + auto-compact. While the recovery logic works, it doesn't prevent the initial expensive roundtrip and the cascade of effects on the client.
-
Tool output truncation exists but may be insufficient:
outputTruncationdefaults to 2000 lines / 50 KB, but a single tool result at the maximum (50 KB ≈ 12,500 tokens) combined with a long conversation history can still exceed the context limit. The truncation also doesn't account for the cumulative token budget across all messages. -
Potential infinite recovery loop: If auto-compact succeeds but the subsequent resume prompt still triggers context overflow,
auto-compacting?has already been reset tofalse, allowing another compact cycle. There is no counter orrecovery-attempted?guard to preventCompact → Resume → Overflow → Compact → …loops (analogous to the existingauto-continued?guard for truncated responses).
Client-side (Emacs)
-
Synchronous JSON parsing of large messages: The process filter in
eca-process.elparses incoming JSON messages synchronously with no size limit. Before the overflow error, the server sendstoolCallednotifications containing the full tool output. Parsing a multi-MB JSON message blocks the Emacs main thread. -
font-lock-ensurerefontifies entire buffer: After each text content insertion (eca-chat.el),font-lock-ensureruns over the entire chat buffer. Ingfm-modewithmarkdown-fontify-code-blocks-natively, this becomes very expensive for large buffers. -
align-tables/beautify-tablesscan entire buffer: On"finished"status, both functions are called with(point-min), scanning the entire chat buffer for markdown tables — expensive for long conversations. -
Expandable content has no size limit: Tool call outputs are stored and rendered in expandable overlays without any truncation. Large outputs bloat the buffer.
These client-side issues compound: large tool output → expensive JSON parse → expensive rendering → expensive fontification → expensive table alignment, all running synchronously on the Emacs main thread.
Steps to Reproduce
- Start an ECA chat session with a model that has a moderate context window (e.g., 128K-168K tokens)
- Have a conversation long enough to consume a significant portion of the context
- Execute a tool/shell command that produces very large output (e.g.,
find / -name "*.log"or similar) - The LLM provider returns HTTP 400 (token limit exceeded)
- Emacs becomes unresponsive
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
Fix potential infinite auto-compact loop on context overflow
ericdallo commentedon Apr 4, 2026
@snoopier I wanna understand better the frozen, can you try latest release, I did some improvements related to loop in compact, but not sure it's what you are seeing.
snoopier commentedon Apr 13, 2026
Using 0.125.0 i can't reproduce because of several other issues.
Maybe you can try a test prompt with Copilot/opus that have lower sessionj limit
first time eca server crashes second time i get
[OPENAI-CHAT] Failed to parse JSON arguments for tool 'eca__write_file': Unexpected end-of-input: expected close marker for Object (start marker at [Source: REDACTED (
StreamReadFeature.INCLUDE_SOURCE_IN_LOCATIONdisabled); line: 1, column: 1])at [Source: REDACTED (
StreamReadFeature.INCLUDE_SOURCE_IN_LOCATIONdisabled); line: 1, column: 44]if i can help/test/whatever contact me :-)
ericdallo commentedon Apr 13, 2026
@snoopier I tested your prompt and it doesn't even get to 30% of context window :/
with this prompt: "read all files in this repo until the context window is blown, do not stop, no subagents", I can fulfill the context, but then the compact is triggered which is expected and everything keeps working.
DevGiuDev commentedon Apr 19, 2026
Happened to me too with a long output coming from `clj -M:cljd test`` with abotu 2.2MB output of errors (ROFL).
This was the tool wich corrupted the sesion.
This was the report from the AI.
Session permanently bricked by untruncated tool output (context window overflow)
Summary
When a tool (particularly
shell_command) returns a very large output, the entire chat session becomes permanently unusable. The oversized output is stored in full in the session history and sent as context to the LLM API on every subsequent call, exceeding the model's context window. The session cannot recover — all future prompts fail silently or with API errors, and the only fix is to start a new conversation.Steps to Reproduce
z-ai/GLM-5.1)clj -M:cljd testwith missing generated test infrastructure (hundreds of repeated Dart compilation errors)flutter buildwith many compilation errorsgrep -rover a large codebase without limitsnpm installwith a large dependency treeObserved Behavior
In my case, the agent ran
clj -M:cljd testwhich produced ~2.2 MB (~560K estimated tokens) of Dart compilation errors (a missinglib/cljd-out/cljd/test.dartcaused every generated test file to fail with repeated errors). This single tool output was stored verbatim indb.transit.json.The model (GLM-5.1, 128K context window) never responded again. The user sent 4 follow-up messages — all ignored:
After switching to an OpenAI model to try to recover, the session produced a secondary error because the OpenAI Responses API rejected the UUID-format message IDs from the previous GLM responses (
Expected an ID that begins with 'rs').Root Cause
shell_commandoutput is stored and sent to the LLM in full, regardless of sizeEvidence
From the session's
db.transit.json:The tool output was a single
shell_commandresult containing hundreds of repeated Dart compilation errors spanning 2.2 MB.Suggested Fixes
Truncate tool outputs — Enforce a maximum size for tool outputs (e.g., 10K–50K chars). Truncate with a message like
[output truncated: X chars omitted]. This should apply both at storage time and when building the API payload.Context overflow detection — Before sending an API request, estimate the token count. If it exceeds the model's context window minus a safety margin, automatically trim or summarize older messages.
Automatic recovery — If an API call returns a context-length error, attempt to recover by:
User feedback on failure — When the API call fails for any reason, display an error to the user instead of silently dropping the response.
Environment
z-ai/GLM-5.1(also confirmed secondary failure withopenai/gpt-5.4-mini)Impact
This is a session-ending bug with no recovery path other than abandoning the conversation. It can be triggered by any command that produces large output, which is a common occurrence during debugging and development workflows.
Looking for a way to reproduce it LLM told me to ask in session to run this:
But I was not able to reproduce it outside the scope of the project.
The only way I can reproduce it was asking to run directlyu the command.
lockie commentedon May 1, 2026
Had similar issue here trying to summarize a huge pile of my email messages using Gmail-MCP. At some point ECA becomes unresponsive, sending any cue ticks for 4 seconds "Generating" and then just stops, doing nothing.
lockie commentedon May 14, 2026
I'm most definitely hit by the problem when the agent gets a lot of output from its MCP calls — it becomes unresponsive and does not react to followup prompts. Perhaps deserves a separate issue though
ericdallo commentedon May 14, 2026
Yeah, please open a new issue with more details and maybe a way to repro that
Auto-compact again when the context refills in the same turn