Remote Control: message arrives in TUI composer but is never submitted (swallowed Enter) — tmux/headless, intermittent

Status Open
Reported on v2.1.211
Maintainer reply None cached
Activity 0 comments · opened Jul 16, 2026

Summary

Messages sent via Remote Control (claude.ai mobile app) to a Claude Code TUI running inside detached tmux intermittently arrive in the composer input box but are never submitted — the text sits after the prompt indefinitely and the turn never starts. To the sender the agent simply appears to ignore the instruction; there is no error on either end.

We hit this 10 times in one day (2026-07-16) across a fleet of always-on headless sessions driven from the mobile app. Examples of stranded texts (visible in the composer, dim-rendered while a turn was active, normal-intensity once idle): keep going autonomously, ok restart the agents now, find and fix the delivery bug that swallows the Enter (yes — the instruction to fix this bug was itself swallowed, twice).

Environment

  • Claude Code 2.1.211 (Linux CLI), Ubuntu 24.04, kernel 6.8
  • TUI running detached in tmux 3.4 (claude --dangerously-skip-permissions --resume <id>), one session per workspace, no attached client
  • Remote Control registered per session; driven from the claude.ai iOS app
  • Sender and host on the same tailnet, but delivery is via the RC service (not SSH/tmux)

Observed behavior

  • The message text does arrive at the TUI — it is visible in the composer (❯ <text>) in a pane capture. So the injection path populates the input buffer, but the submit step is lost.
  • While a turn is running, the stranded text renders dim (same styling as queued input); when the turn ends it remains in the composer at normal intensity and never submits. Text swallowed mid-turn does not self-deliver at turn end.
  • Occurrences cluster in three windows:
  1. TUI busy / turn-start transitions — message sent while a turn was starting or running;
  2. TUI boot — message sent while the session was relaunching (--resume transcript replay on screen);
  3. after RC re-registration churn — we re-registered bridges for many sessions the same day (account changes), leaving orphaned server-side registrations for the same session names; several strandings followed within minutes of re-registration.
  • No correlation with message length or content; all strandings were short single-line texts.

What we ruled out

  • Our own fleet tooling also injects messages via tmux paste-buffer + send-keys Enter, and it had a separate race we found and fixed (verified submit loop). The 10 strandings above are not that path: our delivery tooling keeps a provenance log of every message it injects, and none of the stranded texts appear in it. The only remaining writer for these sessions is the Remote Control path.
  • Not a tmux-attach/human-typing artifact: sessions had no attached clients at the time (session_attached == 0).

Expected

Either the message submits (starts a turn), or delivery fails loudly back to the app ("not delivered"). Silent arrival-without-submit is the worst case: the sender believes the agent received and ignored the instruction.

Repro sketch

  1. Run Claude Code in detached tmux, register Remote Control, drive it from the mobile app.
  2. Send messages specifically during: (a) an active turn, (b) session relaunch while the resume replay is rendering, (c) shortly after the RC bridge for the session name was re-registered.
  3. Capture the pane (tmux capture-pane -p) — the failure signature is the message text sitting after with no turn started.

Intermittent — on a quiet, idle, freshly-connected session we could not force it; under the three windows above it reproduced multiple times per day under real use.

Workaround (for anyone else hit by this)

A cron/systemd watchdog every 2 min: for each tmux session with no attached client and no running turn, capture the pane, and if the same composer text (≥3 printable chars, not the dim placeholder hint) survives two consecutive ticks, press Enter (tmux send-keys Enter) and verify the composer cleared. Converts every stranding into a ≤4-minute self-healing delivery. Caveats we hit implementing it: the empty composer renders a dim Try "..." placeholder that naive captures read as text, and queued mid-turn input also renders dim — distinguish by the hint shape, not the styling alone.

View original on GitHub ↗