Mid-session agent-loop freeze: session permanently stops producing output, no error (v2.1.199 and v2.1.217)
Environment
- Deployment shape: a single long-running Claude Code CLI session per container
lifetime, invoked as claude --dangerously-skip-permissions --model claude-opus-4-8, restarted
--channels <2 chat-platform plugins> --append-system-prompt-file <file>
periodically (auth/error recovery, scheduled context-hygiene recycle, or container
rebuild) — so "one session" below means one CLI process lifetime, one
~/.claude/projects/<cwd-slug>/<sessionId>.jsonl transcript file.
- Model:
claude-opus-4-8(1M-context beta), set via--model, unrelated to the CLI
version under discussion — included here only in case the bugs below are
model/beta-header-interaction-specific rather than pure-CLI bugs.
- Two chat-platform integration MCP plugins are loaded (a Discord-shaped one and a
Telegram-shaped one), plus several first-party MCP servers (browser automation via
Playwright, a graph-database server, a Google-Workspace server). The bugs below are
plugin/MCP-agnostic in principle; Bug 2 was only actually observed in sessions using
the browser-automation (Playwright) MCP server heavily, and one of Bug 3's three
confirmed instances froze immediately after a Playwright tool result — the other two
froze on an idle turn boundary with no tool call involved, so Bug 3 does not appear
to be Playwright-specific the way Bug 2 might be.
- Bug 3's evidence (below) additionally draws on an internal watchdog process (our own
infrastructure, not part of the CLI) that polls session liveness independently of the
CLI and keeps its own status/log stream — included here only as the source of the
wall-clock probe/restart timestamps that corroborate the transcript-level gaps.
- CLI versions in play: 2.1.92 (last known-good), 2.1.199 (the version an
unpinned @latest silently resolved to on 2026-07-02, and the version Bugs 1–2 were
originally reported against), and 2.1.217, deliberately pinned in the deployment's
Dockerfile on 2026-07-22 (it was the actual npm latest/next dist-tag at pin time;
npm's stable dist-tag has since moved independently of what's actually deployed
here) and running in this deployment since ~2026-07-24.
- Update (2026-07-26) — 2.1.217 status for all three bugs, based on the 11 session
files run on 2.1.217 to date (2026-07-24 → present):
- Bug 1 (auto-compaction not firing): unchanged.
grep -c '"isCompactSummary":true'
across all 11 files is 0, same as on 2.1.199.
- Bug 2 (pseudo-invoke artifact): has not recurred on 2.1.217 so far — 0
instances across the 11 files, versus 76 across 4-of-14 sessions on 2.1.199. The
2.1.217 sample is smaller and we have not confirmed it contains sessions as
Playwright-heavy as the worst 2.1.199 ones (the correlation noted under Bug 2), so
we are not claiming this is fixed — only that it hasn't reproduced yet in the
window we've observed.
- Bug 3 (this addendum, mid-session freeze): confirmed present on 2.1.217 —
two new instances, 2026-07-25 and 2026-07-26, both ending in a watchdog-triggered
restart. The instance that originally surfaced this bug (2026-07-18) was on 2.1.199
and self-recovered without a restart; see the Bug 3 section for all three.
- No npm changelog ships with the package (checked: no
CHANGELOG.mdin the
installed package tree), so the regression below is bisectable only to "somewhere
between 2.1.92 and 2.1.199" — not to a specific intermediate version. Narrowing that
would require installing and replaying against intermediate versions, which we have
not done (this report is from read-only inspection of the two versions actually
deployed).
---
Bug 3 — mid-session agent-loop freeze (zero further output, no error)
Symptom
On both 2.1.199 and 2.1.217, the CLI's agent loop occasionally stops entirely,
mid-session, with no further transcript row ever written by the process: no error
row, no isApiErrorMessage, no crash, no process exit. We've now captured three
instances with enough independent corroboration (transcript structure plus an
internal watchdog's own timestamped probe/restart log, described under Environment
above) to be confident this is a real, distinct failure mode and not an artifact of
our own logging. Two row-level shapes, both observed:
- (a) stall after a completed tool call — the last row is a normal, successfully
completed tool_result; the turn simply never continues past it.
- (b) stall before a turn starts — an inbound message is enqueued for the session
(appears in the transcript as a user-role row wrapping the inbound message,
immediately preceded/followed by bookkeeping rows such as attachment /
last-prompt / mode), and no assistant row ever follows — the turn that should
have processed it never begins.
In both shapes, the CLI process itself stays alive throughout: not a zombie, not
exited, no crash signal, sitting in a normal-looking sleep state. Externally this is
indistinguishable from the CLI simply "thinking" until an external prober forces the
question by sending it another message and timing the (non-)response.
Evidence
Summary of the three confirmed instances
| Date (UTC) | CLI version | Row-level shape before the freeze | Freeze duration | How it ended |
|---|---|---|---|---|
| 2026-07-25, ~13:56–14:09 | 2.1.217 | (a) completed tool_result (browser-automation tool) | ~13 min zero-output (≈14.4 min from the pending message's arrival to restart) | Watchdog liveness probe unanswered → container restart |
| 2026-07-26, ~17:01–17:15 | 2.1.217 | (b) inbound message enqueued, turn never starts | ~14 min zero-output from message arrival to restart | Watchdog liveness probe unanswered → container restart |
| 2026-07-18, ~16:46–16:51 | 2.1.199 | (b) inbound message enqueued, turn never starts | ~4.5 min zero-output | Self-recovered — resolved before the watchdog's liveness-probe threshold (10 min) was reached; only a log-only soft-alert fired, no probe, no restart |
Session file size/row-count at the moment of the freeze (i.e., the last byte written
before the gap, or — for the 2026-07-18 instance, which continued afterward — the
byte offset at the freeze point within the eventual final file):
- 2026-07-25 instance: session ended at the freeze (watchdog restarted the container);
final file size ≈21.6 MiB, 1,531 transcript rows.
- 2026-07-26 instance: session ended at the freeze; final file size ≈449 KiB, 392
transcript rows — much smaller than the other two, ruling out "only happens in huge
sessions" as a necessary condition.
- 2026-07-18 instance: freeze occurred at row 1,707 / byte offset ≈22.9 MiB of an
eventual 1,964-row / ≈23.8 MiB file (the session kept running for many more hours
afterward) — i.e., late in an already-large session, similar in that respect to the
2026-07-25 instance even though it self-recovered.
Instance detail: 2026-07-25, ~13:55–14:09 UTC
An inbound message became pending at 13:55Z. The session kept working —three
successive mcp__playwright__click_text tool calls, each followed by itstool_result — through 13:56:11Z, at which point the last tool_result of the
session lands. No further transcript row of any kind (assistant, user, system,
etc.) appears afterward. The watchdog's own independent log shows: a log-only
soft-alert at 14:01Z ("no reply activity for 300s while a message is pending"), a
liveness probe sent at 14:05Z after 10 minutes of no reply to the pending message
(cross-checked: the probe is itself enqueued into the session transcript as aqueue-operation row timestamped 14:05:27Z, matching the watchdog's own log to the
second), and — after 4 more minutes with no output at all — a restart at 14:09:29Z
("failed liveness probe: no session output 4 min after probe; pending 14 min").
Net: ~13 minutes and 17 seconds of zero transcript output, ending in a restart, not a
recovery.
Instance detail: 2026-07-26, ~17:01–17:15 UTC
The prior turn in this session ended cleanly and normally — an assistant row with
ordinary text content, followed by two bookkeeping system rows, timestamped
16:43:42Z. The session then sat idle (expected — no pending work) until a new inbound
message arrived and was enqueued at 17:01:12.717Z. That enqueue is the last row in the
transcript with any real content; two empty attachment rows land at the same
instant, and then — nothing. No assistant row, no further user row, nothing but aqueue-operation row at 17:11:27Z (the watchdog's own probe enqueue) and a barelast-prompt marker; the file ends there at 392 total rows. The watchdog's
independent log: liveness probe sent at 17:11:25Z ("no reply to a message for 10 min;
session also silent for 28 min — not just busy"), restart at 17:15:26Z ("no session
output 4 min after probe; pending 14 min"). Net: ~14 minutes and 14 seconds from
message arrival to restart, with literally zero assistant activity of any kind in
that window — the turn never got as far as a thinking block.
Instance detail: 2026-07-18, ~16:46–16:51 UTC (self-recovered)
Same row-level shape as 2026-07-26: an inbound message is enqueued (transcript
timestamp 16:46:51.258Z) and no assistant row follows immediately. The watchdog
logged a log-only soft-alert 32 seconds later (its 300-second "session has been
generally quiet" threshold was already satisfied independent of this message, since
the session had been idle for over three hours before the message arrived — the
soft-alert is not itself evidence of anything unusual). Unlike the other two
instances, this one recovered on its own: the first assistant row (a thinking
block, followed by text and a tool_use) appears at 16:51:17.018Z — 4 minutes 26
seconds after the message was enqueued — and the session continued normally for many
more hours afterward. The watchdog's own liveness-probe tier (10-minute threshold)
was never reached; only the log-only 5-minute soft-alert fired, confirming
independently that the gap was real but resolved before any external intervention.
One incidental structural detail from the resuming row: it carried acache_read_input_tokens value of 579,147, i.e. the turn that finally executed was
not a small one — noted as a data point, not a claimed cause.
Process-state evidence
Checked live against the current running deployment (not captured during an actual
past freeze, since by the time a freeze is noticed the watchdog has typically already
restarted the container — this establishes the general signature, not a
freeze-specific one):
$ docker exec <mavka container> sh -c 'pgrep -f "^claude --dangerously-skip-permissions"'
7
$ docker exec <mavka container> sh -c 'grep -E "^(Name|State|PPid|Threads):" /proc/7/status; cat /proc/7/wchan'
Name: claude
State: S (sleeping)
PPid: 1
Threads: 12
do_epoll_wait
The long-running claude process is a direct child of the container's PID 1 (the
container's init execs /entrypoint.sh, which execs claude); state S, blocked indo_epoll_wait, not a zombie, not exited. This is the crux of why the bug is
externally invisible: a process idling normally between turns (waiting on its own
event loop/network I/O) produces exactly this same signature, so process-table
inspection alone cannot distinguish "quiet because there's nothing to do" from
"wedged" — only an external prober that sends new input and times the response (as
our watchdog does) can tell the difference. Consistent with the agent loop awaiting
something that never arrives; we make no stronger claim about the cause than that.
Relationship to the 2026-07-13 incident
Bug 2's section above already attributes the 2026-07-13 undelivered-message incident
to the pseudo-invoke artifact (a tool call rendered as prose, not a hang). We checked
that same session's transcript for large silent gaps to see whether any of them
additionally match Bug 3's shape (alive process, zero output, pending message). The
gaps we found are consistent with ordinary overnight/low-traffic idle periods between
this deployment's own scheduled cron-style triggers (recurring gaps aligned to
:00/:30-minute boundaries, hours long, with no pending message sitting unanswered
underneath them) — not the Bug 3 pattern. We are not counting 2026-07-13 as a fourth
Bug 3 instance; it's mentioned only to be explicit that we looked and didn't
double-count it.
Impact
A silent, total stop of the agent loop with no distinguishing signal in the CLI's own
output. In an always-on/unattended deployment, the only reason we know about any of
these three instances at all is that we independently built and run a watchdog that
actively probes for liveness and restarts on failure — without that external
safety net, a frozen session would simply sit indefinitely, appearing "alive" by every
signal the CLI itself exposes (process running, no error, no exit) while never
producing another byte of output. Two of the three instances required a full
container restart to recover, losing whatever in-progress turn state existed at the
freeze point.
Repro difficulty
Intermittent and, so far, not independently reproducible on demand — three instances
observed from organic production use across two CLI versions (2.1.199, 2.1.217), with
no synthetic minimal repro attempted. The 2026-07-18 instance shows the freeze can
also be transient and self-resolve in minutes without any external action, which
complicates repro further: a fixed-duration wait-and-retry test could plausibly "pass"
by accident depending on which side of that variability it lands on. We have not
identified a common trigger across all three instances — one followed a completed
Playwright tool call, the other two occurred at an idle turn boundary right as a new
message arrived, with no tool call involved at all.
---
What we can provide on request
- The exact
grep/stringscommands used above, rerunnable against any 2.1.x
build (they target env var names and function shape, not line numbers, so should
survive minor version bumps).
- Full per-session transcript metadata (file name/sessionId, CLI version, first/last
timestamp, isCompactSummary count) for all 16 sessions referenced above — happy to
share as a table with the sessionId/file-name column removed if that's a concern,
or in full to a private channel upstream can verify us on.
- Redacted transcript excerpts around each of the 76 Bug 2 artifact occurrences
(assistant-turn text only, chat-platform message content stripped) — these would
need to be prepared per-occurrence rather than bulk-dumped, since removing all
message content requires per-file manual redaction; we have not pre-generated this
and would do so if upstream wants to inspect the exact artifact text/context rather
than just the shape quoted above.
- A copy of
scripts/reply-guard.js's detector logic in full (it's already
application code in this public-facing sense — no secrets in it) if a concrete
implementation of the workaround is useful context for triage.
- Sanitized row-structure excerpts around each of the three Bug 3 freeze boundaries
(the handful of transcript rows immediately before and after each gap, row
type/timestamps/field names preserved, all message content and channel
identifiers stripped) — these are already prepared in a shareable form, unlike Bug
2's 76 per-occurrence excerpts which need per-file manual redaction; the independent
watchdog log lines (probe/restart timestamps) corroborating each gap are equally
available.
What we cannot provide
- No root-cause visibility into Bug 3 beyond "the process is alive and idle-shaped
when checked" — we have not attached a debugger/profiler to a live-frozen process
(in practice, by the time a freeze is noticed the watchdog has typically already
restarted the container), so we cannot say what, specifically, the loop is blocked
on.
- We have not reproduced Bug 1 or Bug 2 on any version besides 2.1.92 and 2.1.199 (Bug
2 hasn't recurred at all on 2.1.217 yet — see Environment), so we cannot say whether
Bug 2 is already fixed upstream; Bug 1 remains unchanged on every version we've run
it on (2.1.199 and 2.1.217 both show zero compaction events).
- No tighter bisection range than "somewhere between 2.1.92 and 2.1.199" for Bug 1 —
no intermediate versions were installed/tested.
- No minimal/synthetic repro for Bug 2 or Bug 3 — only organic production occurrences
for both.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗