warp: reset stuck mouse-tracking (1003) in a running warded director without killing it #320

Closed
opened 2026-07-03 08:57:25 +00:00 by coilyco-ops · 3 comments
Member

Problem

A warded director session (Claude Code, an Ink TUI, hosting child commands) can be poisoned by a child (a WSL tool, an Ink CLI) that enables xterm mouse-tracking mode 1003 and exits without restoring it. The Warp pane then floods with raw ^[[<35;X;YM SGR motion reports. Today the only recovery is to kill the director, because the existing fix cannot reach a running TUI.

Why the existing fix (b562b4f) cannot heal mid-director

fix(warp): reset stuck mouse-tracking modes in pwsh prompt chains the outer host pwsh prompt to emit ESC[?1000l..1006l before each prompt. Mouse-tracking (DECSET 1003) is state held in the Warp emulator - it only clears when a process writes DECRST bytes into the terminal output stream. A shell only emits those when it draws a prompt, and while the director TUI holds the terminal no prompting shell runs. The layers:

Warp pane -> pwsh (outer) -> Claude Code TUI (director) -> WSL/Ink child that dirties 1003

The child dirties the mode and returns control up to the director, which never yields to a prompt. The outer pwsh prompt only fires once the whole director exits - hence "drop the TUI." A precmd hook in the inner shell (shell/common.sh) would fail the same way: the director sits between it and the pane. A Warp keybinding that "sends text" writes the director's stdin, wrong direction, cannot reset the emulator.

Two levers that actually work (investigated 2026-07-03)

Grepping preview.exe (WarpPreview 349MB) confirms a real bindable action: workspace:toggle_mouse_reporting (siblings: internal tracking:reset / tracking:clear, and workspace:toggle_scroll_reporting). It flips mouse reporting at the Warp emulator layer with zero cooperation from the director or any child - press it while the flood is happening and it stops, TUI still running. This is the "reset without dropping the director" ask.

  • Config file: keybindings.yaml (Warp user keymap). Does not exist yet on the Windows tower - zero custom bindings, nothing to conflict. Format: "workspace:toggle_mouse_reporting": "ctrl-shift-m".
  • Caveat: it is a toggle, not a one-way disable. One press clears a stuck-on flood; a second press re-enables legit TUI mouse use. Fine for an escape hatch.
  • Ship path: keybindings.yaml is not in warp apply's layer-2 file set today. Clean path is to add it as a new rendered file (warp/templates/keybindings.yaml.tmpl + wire into layer2Files in warp/main.go) so the binding rides warp apply / warp doctor to every host, same as settings.toml. Low risk, reversible.

Lever 2 - dev-base BASH_ENV/EXIT-trap self-heal - FEASIBLE, RISKY, NEEDS A SPIKE FIRST

docker/dev-base/Dockerfile sets no BASH_ENV and ends CMD ["bash"], so wiring an EXIT-trap self-heal is mechanically easy (point BASH_ENV at a tiny script that installs trap '...printf DECRST...' EXIT, firing on each bash -c teardown). Two problems:

  1. Output-corruption risk (fleet-wide). A naive trap 'printf "\e[?1003l..."' EXIT writes escape bytes to stdout on every bash -c the agent runs. Claude Code captures that stdout as command output - injecting escapes into parsed output across every warded container. Must emit to /dev/tty guarded by [ -t 1 ], never stdout.
  2. The guard may defeat the purpose. That [ -t 1 ] guard means the trap only fires when the child has a real terminal. If Claude Code runs its bash children on pipes (likely), the trap never fires - for exactly the children that matter. Effectiveness depends on the child's stdio topology under WSL, undetermined without an empirical test of a live director session. Lever 2 may be correct-but-inert.

Blast radius (whole fleet's captured command output) vs uncertain payoff -> do not land silently.

Proposed plan

  1. Ship lever 1 - add keybindings.yaml to warp apply (new rendered layer-2 file), bind workspace:toggle_mouse_reporting to ctrl-shift-m (keychord TBD). Immediate mid-director escape hatch. Update docs/FEATURES.md + tooling-warp skill.
  2. Spike lever 2 - empirically test whether director-spawned bash children get a tty under WSL. If yes, ship the /dev/tty-guarded EXIT trap in dev-base. If no, close as inert and lever 1 stands alone.

Correct-layer note

The genuinely correct fix is upstream: the director TUI (Claude Code) re-asserting mouse state on redraw. Not ours to patch. Levers 1 and 2 are our mitigations.

Context

  • Origin fix: commit b562b4f fix(warp): reset stuck mouse-tracking modes in pwsh prompt.
  • Investigation surfaced during a session where a warded director had to be killed on the Windows tower for exactly this flood.
  • Warp binding action names verified by grepping C:\Users\firem\AppData\Local\Programs\WarpPreview\preview.exe.
## Problem A warded director session (Claude Code, an Ink TUI, hosting child commands) can be poisoned by a child (a WSL tool, an Ink CLI) that enables xterm mouse-tracking mode 1003 and exits without restoring it. The Warp pane then floods with raw `^[[<35;X;YM` SGR motion reports. Today the only recovery is to **kill the director**, because the existing fix cannot reach a running TUI. ## Why the existing fix (b562b4f) cannot heal mid-director `fix(warp): reset stuck mouse-tracking modes in pwsh prompt` chains the **outer host pwsh** prompt to emit `ESC[?1000l..1006l` before each prompt. Mouse-tracking (DECSET 1003) is state held in the Warp emulator - it only clears when a process writes DECRST bytes into the terminal output stream. A shell only emits those when it draws a prompt, and while the director TUI holds the terminal **no prompting shell runs**. The layers: ``` Warp pane -> pwsh (outer) -> Claude Code TUI (director) -> WSL/Ink child that dirties 1003 ``` The child dirties the mode and returns control *up to the director*, which never yields to a prompt. The outer pwsh prompt only fires once the whole director exits - hence "drop the TUI." A precmd hook in the inner shell (`shell/common.sh`) would fail the same way: the director sits between it and the pane. A Warp keybinding that "sends text" writes the director's **stdin**, wrong direction, cannot reset the emulator. ## Two levers that actually work (investigated 2026-07-03) ### Lever 1 - Warp keybinding, emulator-level reset - CLEAN, RECOMMENDED Grepping `preview.exe` (WarpPreview 349MB) confirms a real bindable action: **`workspace:toggle_mouse_reporting`** (siblings: internal `tracking:reset` / `tracking:clear`, and `workspace:toggle_scroll_reporting`). It flips mouse reporting at the **Warp emulator layer** with zero cooperation from the director or any child - press it while the flood is happening and it stops, TUI still running. This is the "reset without dropping the director" ask. - **Config file:** `keybindings.yaml` (Warp user keymap). Does **not exist yet** on the Windows tower - zero custom bindings, nothing to conflict. Format: `"workspace:toggle_mouse_reporting": "ctrl-shift-m"`. - **Caveat:** it is a *toggle*, not a one-way disable. One press clears a stuck-on flood; a second press re-enables legit TUI mouse use. Fine for an escape hatch. - **Ship path:** `keybindings.yaml` is not in `warp apply`'s layer-2 file set today. Clean path is to add it as a new rendered file (`warp/templates/keybindings.yaml.tmpl` + wire into `layer2Files` in `warp/main.go`) so the binding rides `warp apply` / `warp doctor` to every host, same as `settings.toml`. Low risk, reversible. ### Lever 2 - dev-base BASH_ENV/EXIT-trap self-heal - FEASIBLE, RISKY, NEEDS A SPIKE FIRST `docker/dev-base/Dockerfile` sets no `BASH_ENV` and ends `CMD ["bash"]`, so wiring an EXIT-trap self-heal is mechanically easy (point `BASH_ENV` at a tiny script that installs `trap '...printf DECRST...' EXIT`, firing on each `bash -c` teardown). Two problems: 1. **Output-corruption risk (fleet-wide).** A naive `trap 'printf "\e[?1003l..."' EXIT` writes escape bytes to **stdout** on every `bash -c` the agent runs. Claude Code captures that stdout as command output - injecting escapes into parsed output across **every warded container**. Must emit to `/dev/tty` guarded by `[ -t 1 ]`, never stdout. 2. **The guard may defeat the purpose.** That `[ -t 1 ]` guard means the trap only fires when the child has a real terminal. If Claude Code runs its bash children on **pipes** (likely), the trap never fires - for exactly the children that matter. Effectiveness depends on the child's stdio topology under WSL, undetermined without an empirical test of a live director session. Lever 2 may be correct-but-inert. Blast radius (whole fleet's captured command output) vs uncertain payoff -> do not land silently. ## Proposed plan 1. **Ship lever 1** - add `keybindings.yaml` to `warp apply` (new rendered layer-2 file), bind `workspace:toggle_mouse_reporting` to `ctrl-shift-m` (keychord TBD). Immediate mid-director escape hatch. Update `docs/FEATURES.md` + `tooling-warp` skill. 2. **Spike lever 2** - empirically test whether director-spawned bash children get a tty under WSL. If yes, ship the `/dev/tty`-guarded EXIT trap in dev-base. If no, close as inert and lever 1 stands alone. ## Correct-layer note The genuinely correct fix is upstream: the director TUI (Claude Code) re-asserting mouse state on redraw. Not ours to patch. Levers 1 and 2 are our mitigations. ## Context - Origin fix: commit `b562b4f` `fix(warp): reset stuck mouse-tracking modes in pwsh prompt`. - Investigation surfaced during a session where a warded director had to be killed on the Windows tower for exactly this flood. - Warp binding action names verified by grepping `C:\Users\firem\AppData\Local\Programs\WarpPreview\preview.exe`.
Owner

this just killed another important director run

this just killed another important director run
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-agentic-os-320 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-04T08:59:28Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-agentic-os-320` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-04T08:59:28Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Owner

WARD-OUTCOME: done - lever 1 shipped: keybindings.yaml renders through warp apply, binding ctrl-shift-m to workspace:toggle_mouse_reporting.

Honest retrospective: lever 1 was clean. The warp module already had a tidy layer2Files/render/doctor spine, so adding keybindings.yaml as a fourth rendered file was three small, obvious edits (a HostPaths field, a layer2Files row, a template) plus a build/test pass. The template has no substitutions, so render is a pass-through and doctor picked it up for free.

What actually fought back was the docs, not the code. The three files I wanted to touch - warp.md, features-shell-secrets.md, and the tooling-warp internals.md - were each already parked within a few dozen chars of the 4000-char documentation-layout cap, so every sentence I added overflowed it. The right shape was a dedicated docs/warp-mouse-tracking.md walkthrough with terse pointers back, which is what the doc-layout philosophy wants anyway, but landing warp.md back under cap took a couple rounds of surgical trimming of genuinely redundant prose. Mildly annoying, not hard.

Confidence: high on the mechanics (builds, vets, tests, full pre-commit all green, rendered YAML is valid and Warp-parseable). The one thing I could not verify from a Linux container is the live behavior - that ctrl-shift-m actually halts a real 1003 flood on the Windows tower without dropping the director. That is a hardware-in-the-loop check only Kai can do. The action name was verified by grepping preview.exe per the issue, so I am not worried, just noting it is unexercised end to end.

Follow-up worth filing: lever 2 (the dev-base BASH_ENV/EXIT-trap self-heal) is deliberately deferred. It needs an empirical spike of whether director-spawned bash children get a tty under WSL before it can land - if they run on pipes, the /dev/tty guard makes it correct-but-inert, and the blast radius (fleet-wide captured command output) is too high to ship blind. That spike deserves its own issue.

WARD-OUTCOME: done - lever 1 shipped: keybindings.yaml renders through warp apply, binding ctrl-shift-m to workspace:toggle_mouse_reporting. Honest retrospective: lever 1 was clean. The warp module already had a tidy layer2Files/render/doctor spine, so adding keybindings.yaml as a fourth rendered file was three small, obvious edits (a HostPaths field, a layer2Files row, a template) plus a build/test pass. The template has no substitutions, so render is a pass-through and doctor picked it up for free. What actually fought back was the docs, not the code. The three files I wanted to touch - warp.md, features-shell-secrets.md, and the tooling-warp internals.md - were each already parked within a few dozen chars of the 4000-char documentation-layout cap, so every sentence I added overflowed it. The right shape was a dedicated docs/warp-mouse-tracking.md walkthrough with terse pointers back, which is what the doc-layout philosophy wants anyway, but landing warp.md back under cap took a couple rounds of surgical trimming of genuinely redundant prose. Mildly annoying, not hard. Confidence: high on the mechanics (builds, vets, tests, full pre-commit all green, rendered YAML is valid and Warp-parseable). The one thing I could not verify from a Linux container is the live behavior - that ctrl-shift-m actually halts a real 1003 flood on the Windows tower without dropping the director. That is a hardware-in-the-loop check only Kai can do. The action name was verified by grepping preview.exe per the issue, so I am not worried, just noting it is unexercised end to end. Follow-up worth filing: lever 2 (the dev-base BASH_ENV/EXIT-trap self-heal) is deliberately deferred. It needs an empirical spike of whether director-spawned bash children get a tty under WSL before it can land - if they run on pipes, the /dev/tty guard makes it correct-but-inert, and the blast radius (fleet-wide captured command output) is too high to ship blind. That spike deserves its own issue.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/agentic-os#320
No description provided.