fix(sweep): tmux - delivery retries, REPL death detection, transcript swap race - #713
Conversation
…data-loss, dream runner hardening - tmux_transcript.py: swap-generation guard so a concurrent set_transcript_path during an awaited turn callback discards the old file's remaining chunk instead of leaking dead-session text and corrupting the new offset - tmux_session.py: attempt_reconnect now front-enqueues an orientation wake prompt after respawn (force_restart/#589 parity), so heartbeat-resurrected agents are not orientationless - tmux_session.py: worker retries turn delivery on tmux command timeouts (bounded budget) and routes a delivery-failure notice to the sending chat on permanent failures instead of dropping messages silently - tmux_session.py: stats pending_responses now counts in-flight turns + queue backlog instead of the sub-second paste window, fixing the UI busy badge and the session_watchdog backlog gate - tmux_dream_runner.py: _tmux subprocess calls get a 5s timeout (kill on expiry) so a hung tmux server cannot wedge the dream task and the #704 overlap guard - tmux_dream_runner.py: _wait_for_result checks REPL liveness each poll (remain-on-exit + pane_dead probe) and fails fast with the pane tail instead of burning the full 1h timeout - tmux_dream_runner.py: prompt/result files are deleted after successful runs (kept on failure for post-mortem) so dreams/ no longer grows unbounded - tests: regression coverage in test_tmux_transcript.py, test_tmux_session.py, test_tmux_dream_runner.py Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
olegbrok
left a comment
There was a problem hiding this comment.
Barsik review — needs-changes (one major)
Five of seven fixes verified correct and well-tested — the transcript swap race (generation counter + early-return-before-offset-advance) and the orientationless reconnect wake re-prime are genuine bugs with clean fixes. Effort/--effort wiring (#650) untouched. No semantic conflict with #531/#612/#613 (note: #531 and #612 currently carry an identical _verify_repl_survived_launch diff — reconcile those two separately; whichever merges after #713 needs a routine rebase, disjoint hunks).
- (major) tmux_session.py:1409-1422 — the
pending_responsesstats change silently arms the outer session watchdog for tmux. Pre-PR, pending was effectively always 0 so the 10-min warn / 15-min auto-recover never fired for tmux. Post-PR any in-flight turn counts as backlog, and the outer watchdog has NONE of the inner_inflight_watchdog's liveness carve-outs (transcript-recently-grew,_background_tasks_recently_activefrom #692, live_status idle floor). Concrete regression: a long turn parked on a background Workflow/Agent fan-out (real production pattern — it bit me TODAY at 15:39, mid-review) gets warn-alerted at 10 min and disconnect-recovered mid-turn at 15 min, reintroducing through the outer watchdog exactly the kill #692 removed from the inner one. Fix: count only_message_queue.qsize()toward the backlog gate (expose inflight separately), or surface busy-evidence in stats and gate the watchdog on it. - (minor) tmux_dream_runner.py:317-334 —
_repl_alivetreats rc=124 probe-timeout as death; one transiently hung tmux probe at 3am kills the night's dream. Requirepane_dead==1/'can't find session', or two consecutive dead probes. - (minor) tmux_dream_runner.py:347-353 — liveness probe is in the
elif: if the REPL creates a 0-byte result file then dies, death is never detected — burns the full 1h timeout. Probe liveness when size isn't growing too. - (minor) delivery-retry duplicate window: a timeout on the final send-keys Enter where tmux actually processed it re-submits the turn (up to 3×) — side-effecting instructions can run twice; deserves an explicit comment + ideally a capture-pane input check before re-paste.
Fix the watchdog finding (plus ideally the rc=124 false-death) and this is merge-ready.
- stats: pending_responses counts only queue backlog; in-flight turns are exposed separately as inflight_turns (UI busy state reads both), so a long-running turn no longer arms the outer session watchdog, which lacks the inner _inflight_watchdog's liveness carve-outs - dream runner: _repl_alive is tri-state; an rc=124 probe timeout is indeterminate, not death - aborting needs pane_dead/session-gone or two consecutive dead probes - dream runner: probe REPL liveness whenever the result file is not growing, so a 0-byte result file plus a dead REPL fails fast instead of burning the full timeout - worker: explicit duplicate-submit comment plus a capture-pane guard before re-pasting a timed-out turn; a landed paste is recorded as delivered instead of re-submitted - dream runner: annotate best-effort except clauses (kill race, cleanup) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All four findings addressed in 1b845bd:
382 tests across tmux/watchdog suites pass; full 15-branch octopus suite green locally. Generated with Claude Code |
olegbrok
left a comment
There was a problem hiding this comment.
Barsik re-review — approve ✅ (hold merge for CI on head SHA)
All four findings substantively fixed at 1b845bd, not renamed-around:
pending_responses= queue.qsize() only; inflight exposed separately asinflight_turns(option A, with a comment naming the watchdog gate). Verified the watchdog's require_backlog gate now short-circuits on long in-flight turns with empty queue — the 15-min mid-turn auto-recover is closed; UI consumers updated to sum both keys._repl_aliveis tri-state — rc=124 probe-timeout is indeterminate; abort needs definitive pane-death or 2 consecutive failed probes. Both tested.- Liveness probed whenever the result file isn't growing (absent/0-byte/stalled) — 0-byte+dead-REPL detected <5s in test. remain-on-exit at spawn makes pane_dead observable.
_timed_out_turn_landedcapture-pane guard before re-paste, with full delivery bookkeeping via_finish_turn_delivery(response routing preserved — checked it's not stats-only).
Non-blocking nits: marker false-positive possible when a prompt's first 40 chars match a previous turn still in scrollback (wake-prompt boilerplate) — worth a comment; stale old-semantics comment at tmux_session.py:132.
Merge condition: Python 3.11/3.12/3.13 test jobs were still in_progress and 'Build + smoke (amd64)' hit a Buildx setup flake at review time — re-run the flake and wait for the head SHA to conclude green before merging.
Summary
Part of a full-codebase bug sweep: every finding below came from a multi-agent audit and was independently re-verified against the code before fixing. Fixes are minimal and scoped to the files cited; no two sweep PRs touch the same source file, so they can merge in any order.
Findings fixed
src/pinky_daemon/tmux_transcript.py:658)src/pinky_daemon/tmux_session.py:5192)src/pinky_daemon/tmux_session.py:4092)src/pinky_daemon/tmux_dream_runner.py:273)src/pinky_daemon/tmux_dream_runner.py:125)src/pinky_daemon/tmux_session.py:1407)src/pinky_daemon/tmux_dream_runner.py:145)Testing
tmux: python3 -m pytest tests/test_tmux_session.py -q -> 261 passed (includes 6 new regression tests); python3 -m pytest tests/test_tmux_transcript.py tests/test_tmux_dream_runner.py -q -> 58 passed (1 new transcript swap-race test, 3 new dream-runner tests, 1 happy-path test updated for file cleanup); python3 -m pytest tests/test_session_watchdog.py tests/test_agent_status.py tests/test_transport.py tests/test_tmux_context_autorestart.py tests/test_tmux_trust_seed.py tests/test_tmux_container_isolation.py -q -> 179 passed; python3 -m pytest tests/test_api.py -q -k tmux -> 3 passed; python3 -m pytest tests/test_broker.py -q -> 33 passed; ruff check on all 6 changed files -> All checks passed. No failures anywhere.
Generated with Claude Code