Channel mode: injected draft never auto-submits into a turn, causing watchdog false-restart (~every 30 min, context lost)

Resolved 💬 2 comments Opened Jun 9, 2026 by Noah0025 Closed Jun 9, 2026

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=0 throughout (the inbound message was already consumed by the bridge).
  • The busy_until lease essentially never appears (only 2 occurrences in entire history) → PreToolUse never 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

  1. Run claude inside a tmux pane as a persistent channel/bot bridge.
  2. From an external process, write each inbound message into the tmux input pane (the pattern used by the official Telegram channel plugin).
  3. Drive normal request/response turns over an extended period.
  4. 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-official Telegram 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_until lease absent → PreToolUse never triggered → turn never started; draft does not persist across restart.
  • Our server.ts trace instrumentation has a blind spot here: in the run-up to a draft-stuck kill there are sometimes zero INBOUND_DELIVER events 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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗