Channel mode: injected draft never auto-submits into a turn, causing watchdog false-restart (~every 30 min, context lost)
What's Wrong?
Running Claude Code as a long-lived "channel" (a tmux-injected bot bridge — the official claude-plugins-official Telegram plugin v0.0.6, where an external Bun process writes incoming messages into the CLI's tmux input pane), the session is force-restarted roughly every 20–30 minutes, wiping the current conversation context each time (on-disk memory is unaffected; the live in-session turn history is lost).
The dominant trigger is an input draft that never gets auto-submitted into a turn. An incoming message is written into the CLI input box and becomes a draft, but the CLI does not convert it into a new turn (auto-submit never fires). The draft then sits idle until an external watchdog (300s timeout) classifies it as a deadlock and kills/restarts the process.
On 2026-06-09 we observed 8 restarts in one day; 6 of them were draft-stuck (the other 2 were a separate reply-not-sent issue). The machine was confirmed awake the whole time (no launchd heartbeat gap >320s), so this is not a sleep/wake artifact.
What Should Happen?
A message that lands in the input box as a draft should be reliably auto-submitted as a new turn. Alternatively (or additionally), the CLI should expose draft/turn state (e.g. "draft present but no turn started") so an external health checker can distinguish a genuinely stuck draft from a healthy idle state, instead of having to guess via a blind timeout.
Error Messages / Logs
Watchdog kill reason recorded in launchd.log (our wrapper's RESTART_SESSION echo):
RESTART_SESSION reason="input draft stuck 301s"
Representative evidence from our injected server.ts trace (turn-trace-*.log) for a draft-stuck kill (victim pid 88961): last OUTBOUND_CALL at 12:55:54 CEST, then no INBOUND_DELIVER of any kind before the 13:09 kill — yet a draft was sitting in the tmux input pane aged ~601s.
Key signals across all draft-stuck samples:
- Draft age lands exactly at
301s/601s(crossing watchdog ticks) — i.e. the timeout, not a real workload. pending=0throughout (the inbound message was already consumed by the bridge).- The
busy_untillease essentially never appears (only 2 occurrences in entire history) →PreToolUsenever fired → no turn ever started. - The stuck draft does not survive a restart (it is bound to the session input box, not a real queued message).
Steps to Reproduce
- Run
claudeinside a tmux pane as a persistent channel/bot bridge. - From an external process, write each inbound message into the tmux input pane (the pattern used by the official Telegram channel plugin).
- Drive normal request/response turns over an extended period.
- Intermittently (~every 20–30 min under real traffic), an inbound message becomes a draft in the input box but never auto-submits into a turn; it sits until the watchdog's 300s timeout force-restarts the session, discarding live context.
Reproduction is non-trivial because it depends on a channels + tmux-injection deployment rather than standard interactive use, and the failure is intermittent.
Claude Code Version
2.1.169 (npm latest at time of report)
Environment
- Platform: Claude API (1st-party)
- Operating System: macOS 26.5.1 (Build 25F80, Darwin 25.5.0), arm64 / Apple Silicon
- Terminal/Shell: tmux 3.6b + zsh
- Channel:
claude-plugins-officialTelegram plugin v0.0.6 (Bun-driven, tmux-injection bridge) - Regression: unknown — observed on 2.1.169; changelog 2.1.143→2.1.169 contains no entry touching draft/channel/auto-submit/tmux-injection/session-restart, so we have no known last-working version.
Root-Cause Analysis
We instrumented the channel and isolated two independent failure modes; this report is specifically about the draft-stuck one (CLI-internal):
- An inbound message reaches the tmux input box and becomes a draft, but auto-submit does not fire, so no turn begins. The draft idles 300s and the external watchdog kills the session.
- Diagnostic fingerprints: draft age pinned at
301s/601s;pending=0(message already consumed);busy_untillease absent →PreToolUsenever triggered → turn never started; draft does not persist across restart. - Our
server.tstrace instrumentation has a blind spot here: in the run-up to a draft-stuck kill there are sometimes zeroINBOUND_DELIVERevents despite a stuck draft existing — meaning the path "became a draft but was never delivered / never started a turn" is not observable from the plugin layer. This points to the behavior being internal to the CLI (between receiving input-box content and starting a turn).
(The other failure mode — model emitting text+end_turn without calling the channel's reply tool, so the answer is generated but never sent — is a separate behavioral issue and is not what this report covers.)
Additional Information
We can provide trace excerpts (turn-trace logs with INBOUND_DELIVER/INBOUND_ACK/OUTBOUND_CALL markers) and the watchdog timing data on request. The most useful CLI-side fix would be either (a) guaranteeing input-box content is auto-submitted into a turn, or (b) exposing draft-vs-turn state for external health checks.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗