Skip to content

fix(chat): refuse chat.send on sessions live in tmux and register the omo runtime - #47

Merged
devswha merged 1 commit into
mainfrom
fix/live-spawn-guard
Aug 13, 2026
Merged

fix(chat): refuse chat.send on sessions live in tmux and register the omo runtime#47
devswha merged 1 commit into
mainfrom
fix/live-spawn-guard

Conversation

@devswha

@devswha devswha commented Aug 13, 2026

Copy link
Copy Markdown
Owner

What

Fixes #44. chat.send routed every message to spawnFns[provider] without checking whether the target transcript is currently owned by a live tmux pane, so a second headless CLI resumed the same session id: two processes interleaved one JSONL and the live agent never saw the message.

How

  • New live-spawn-guard.service.ts: before starting a run, chat.send resolves the fresh external-CLI roster (getExternalCliSessionsDetailedFresh, same evidence grade as request-time relay authorization) and refuses with a SESSION_LIVE_IN_TMUX protocol error naming the owning tmux session. The client already renders protocol errors in the conversation and stops the spinner.
  • Fail-open on unavailable discovery evidence: a false block would break the core chat path outright; a false allow merely restores the pre-guard behavior, and with no tmux server running no pane can own a transcript anyway.
  • Guarded providers: the six headless-resume CLIs (claude, codex, cursor, opencode, omp, omo). gjc is exempt — its live sessions are reached through the SDK connect lane, which attaches to the running agent instead of forking one.
  • omo send runtime registered (spawnOmo/abortOmoSession), closing the gap fix(providers): register omo in the runtime lists the type system cannot see #43 deliberately left pending exactly this guard. The pi abort-handle capture and chat.abort failure branch include omo alongside gjc/omp.

Verification

  • New pure-matcher regression tests (live-spawn-guard.test.ts): exact provider+id match blocks, unresolved panes / other providers / other ids never block, gjc exempt.
  • Real-host E2E: started omo --session-id <id> in a tmux pane → findLiveTmuxSpawnBlock('omo', id) returned {"tmuxName":"guard-e2e"}; unrelated id and same id under omp returned null; killing the pane released the guard.
  • Full suite: server 930 + client 262, 0 fail. typecheck + eslint clean.

… omo runtime

chat.send routed every message to spawnFns[provider] without checking
whether the target transcript is currently owned by a live tmux pane, so a
second headless CLI resumed the same session id: two processes interleaved
one JSONL and the live agent never saw the message (#44, observed on omo).

- New live-spawn-guard service: before starting a run, chat.send resolves
  the fresh external-CLI roster and refuses with SESSION_LIVE_IN_TMUX when a
  pane of the same provider owns the exact provider session id. Fail-open on
  unavailable discovery evidence: a false block would break the core chat
  path, a false allow merely restores pre-guard behavior.
- Guarded providers are the six headless-resume CLIs (claude, codex, cursor,
  opencode, omp, omo). gjc is exempt: its live sessions are reached through
  the SDK connect lane, which attaches instead of forking.
- With the guard in place, spawnOmo/abortOmoSession are registered, closing
  the omo send gap left by #43. The pi runtime abort-handle capture and the
  chat.abort failure branch now include omo alongside gjc/omp.

Verified end to end on this host: a real omo pane started with
--session-id <id> in tmux blocks the spawn with the owning session name,
an unrelated id and the same id under omp pass, and killing the pane
releases the guard.

Closes #44
@devswha
devswha merged commit 84d0b67 into main Aug 13, 2026
2 checks passed
@devswha
devswha deleted the fix/live-spawn-guard branch August 13, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chat.send can start a second CLI process on a session that is already live in tmux

1 participant