Tasks: stop the respawn colliding with the session it replaces (issue-146)
Phase 3 of 3. Derived from the locked
design.md. TDD (tdd.mode: standard): the failing test comes first for every behavioural task.
Dependency graph (DAG)
mermaid
flowchart LR
T1[T1 probe tests] --> T2[T2 exit_code + session_state]
T2 --> T3[T3 has_session/has_live_session]
T3 --> T4[T4 spawn pre-flight tests]
T4 --> T5[T5 _clear_target + duplicate-session retry]
T5 --> T6[T6 eventlog vocabulary]
T6 --> T7[T7 _respawn_tmux routes into a live occupant]
T7 --> T8[T8 session-occupied is skipped, not retried]
T8 --> T9[T9 _spawn_tmux refuses instead of killing]
T5 --> T10[T10 stub knob: dead-until-spawn]
T10 --> T11[T11 integration: respawn averted]
T9 --> T11
T11 --> T12[T12 integration: duplicate session skipped]
T12 --> T12b[T12b regression: killed tmux keeps the conversation]
T6 --> T12c[T12c poll ledger records give-ups]
T12c --> T12d[T12d version-gated re-arm]
T12b --> T13[T13 lint + typecheck + full test run]
T12d --> T13
T13 --> T14[T14 capability + config docs, decision-055]
T14 --> T15[T15 PR + reviewer briefing]Task list
- [x] T1 — Failing probe unit tests. In
cli/tests/test_tmux_runner.py:session_statereturnslive/dead/absent/unknown, withunknowndriven by asubprocess.TimeoutExpiredprobe and by a missing binary; thehas_session/has_live_sessiontruth table of design §2.1. (AC1, AC2) - [x] T2 —
TmuxResult.exit_code+session_stateincli/the_loop/runner.py:_runrecords tmux's exit status and leaves itNonewhen tmux never answered;session_stateclassifies from it plus the pane read. (AC1) - [x] T3 — Re-express
has_live_sessionoversession_state, treatingunknownas live (its documented contract).has_sessionkeeps its unknown→False reading and stays a single existence-only call — its callers (terminate_harness,sessions attach) need no pane read. (AC2) - [x] T4 — Failing spawn pre-flight tests.
spawnagainst a live occupant issues neitherkill-sessionnornew-sessionand returnssession_exists/session_live; against a dead occupant it clears and spawns; when the clear is unverified it refuses; aduplicate sessionfromnew-sessionre-probes and retries once. (AC3, AC4, AC5) - [x] T5 —
session_exists/session_live+_clear_targetinrunner.py, and the single-retryduplicate sessionhandler inspawn. (AC3, AC4, AC5) - [x] T6 — Event-log vocabulary.
session.respawn_avertedand thesession-occupiedreason ondispatch.droppedincli/the_loop/eventlog.py. (AC10) - [x] T7 —
_respawn_tmuxasks first, then routes. The openingsession_state == livecheck and_deliver_into_occupant(paste, mark processed, graph link,session.respawn_averted; transient failure →dispatch.failed+ released), plus thesession_exists && session_livebranch of both spawn call sites. Unit tests incli/tests/test_tmux_runner.py/ the dispatcher tests first. (AC6, AC7, AC9) - [x] T8 —
session-occupiedis skipped, not retried.session_exists && not session_live→dispatch.droppedat error level with the delivery id kept; assert the deduper still holds it. (AC8) - [x] T9 —
_spawn_tmuxrefuses a live occupant withsession.spawn_failednaming the manual remedy, instead of killing it. (AC3) - [x] T10 — The stub tmux tracks which sessions exist. Rather than renaming the pane knob (the original plan),
cli/tests/test_tmux_runner_integration.py's stub now models session lifetime —$STUB_TMUX_EXISTINGplus every recordednew-session, minus every successfulkill-session— and answershas-sessiontruthfully, refusing anew-sessionon a held name with tmux's ownduplicate session. That makes absence the default (so the resume tests need no pane knob at all) and makes the collision expressible, which the old always-succeeds stub could not do. Adds$STUB_TMUX_SLOWfor a probe timeout. - [x] T11 — Integration: the respawn is averted. Gherkin +
Requirement: docs/specs/issue-146/bugfix.md#AC6: delivery finds the pane dead, the target is live by respawn time → the event is pasted into it, nonew-sessionis invoked,session.respawn_avertedis logged, the delivery is marked processed. (AC6) - [x] T12 — Integration:
duplicate sessionis skipped, not looped. Stubnew-sessionreportsduplicate sessionfor a dead, unclearable occupant →dispatch.dropped/session-occupied, the delivery id is not released, and no second spawn is attempted. (AC5, AC8) - [x] T12b — Regression: a killed tmux session keeps its conversation. Gherkin +
Requirement: docs/specs/issue-146/bugfix.md#AC12: the registry holds an active tmux-mode session, its tmux session is gone, an event arrives → the respawn argv isclaude --resume <recorded id>and the registry keeps that id. Also assert the resume survives an unanswered liveness probe (thesurvived()half of the same fix). (AC12) - [x] T12c — The poll ledger records a give-up as a give-up.
PollState.resolve_comment(..., gave_up=True)writesgaveUp: {comments, version};_process_commentpasses it at the budget boundary;finalizeprunes the record to live comment ids. Unit tests incli/tests/test_poller.pyfirst. (AC11) - [x] T12d — Version-gated, once-per-run re-arm.
PollState.rearm_gave_up_comments(ref)(no-op when the recorded version equalsthe_loop.__version__), called once per item per run from_process_item, emittingpoll.rearmed. Tests: an older version re-arms and the comment is re-forwarded with a fresh budget; the same version re-arms nothing across repeatedpoll --onceruns. (AC11) - [x] T13 —
make lint typecheck test(ruff + pyright + pytest, plus markdownlint on the docs) — same commands the hooks and CI run. - [x] T14 — Docs.
docs/capabilities/interactive-sessions.md(current behaviour + history row), thetmux.keepSessionOnClose/tmux.resumeOnRespawnparagraphs indocs/config/cli/routing-options.mdthat still promise a reclaimed name, anddocs/decisions/decision-055.md+decisions.mdfor "never spawn over a liveloop-<slug>; route into it". - [x] T15 — PR + reviewer briefing from the bundled template, phase label →
loop:needs-review, execution log updated. (PR #147) - [x] T16 — Spec artifacts clear the-loop's own PDLC gate.
the-loop check issue-146 --recompute --fail-on block(what thegatejob runs) requires the exact headingspdlc.yamlnames —## Requirements/## Security considerationsinbugfix.md,## Architecture/## Security design/## Testing strategyindesign.md,## Task listhere — and every checkbox ticked. Restructured to the bundled templates' vocabulary rather than paraphrases of it, and the upstream security markers (trust boundary,abuse case) named in both artifacts soenforces-boundaries-fromactually runs instead of skipping on a near-miss word.