fix(tmux): synchronize controlModeDone reads to fix data race - #193
Closed
tstapler wants to merge 5 commits into
Closed
fix(tmux): synchronize controlModeDone reads to fix data race#193tstapler wants to merge 5 commits into
tstapler wants to merge 5 commits into
Conversation
…nsition bugs, live state much improved Full backlog-feature-improvement skill re-run: ListStuckBacklogItems down to 4 items (best reading this doc has recorded), root-caused the 2 stale orphaned_triage items to host swap exhaustion + a missing remediation wiring (not a new lost-event bug). Found 4 new CRITICAL + 4 new MAJOR instances of the recurring swallowed-status-transition shape via parallel architecture/code/ux review passes, plus confirmed the pipeline-mode picker and review-gate PipelineEngine coverage gaps are now closed. Also relocates BUG-042's doc to docs/bugs/fixed/ — its commit (b6e76be) merged 2026-07-25 but the doc was never moved. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3683CH7Fs9zYR2yP3Dpba
…nsition bugs, live state much improved Full backlog-feature-improvement skill re-run: ListStuckBacklogItems down to 4 items (best reading this doc has recorded), root-caused the 2 stale orphaned_triage items to host swap exhaustion + a missing remediation wiring (not a new lost-event bug). Found 4 new CRITICAL + 4 new MAJOR instances of the recurring swallowed-status-transition shape via parallel architecture/code/ux review passes, plus confirmed the pipeline-mode picker and review-gate PipelineEngine coverage gaps are now closed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3683CH7Fs9zYR2yP3Dpba
Content edit on top of the rename commit — records the fix commit (b6e76be, merged 2026-07-25) in the doc's status line to match the docs/bugs/fixed/ convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3683CH7Fs9zYR2yP3Dpba
…mtg-ai#274/smtg-ai#275 outcomes The swap-exhaustion claim conflated "swap used" with "active memory pressure" — free -h's available column (24Gi) at the same moment shows the host almost certainly wasn't under live pressure at audit time, just carrying a stale swap footprint from an earlier incident. Also records the three draft PRs opened this session in response to the audit's routing recommendations. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3683CH7Fs9zYR2yP3Dpba
readControlModeOutput and monitorControlModeErrors each captured t.controlModeDone without holding controlModeSubMu, racing against StopControlMode's synchronized close+nil of the same field under that lock. Confirmed by `go test -race` in CI (WARNING: DATA RACE at control_mode.go:167 write vs. :322 read), pre-existing on main and newly surfaced now that the LFS quota CI blocker is fixed and -race tests can run again. Both reader goroutines follow the same "capture doneCh before StopControlMode can nil it" pattern, so both needed the same fix for consistency, not just the one flagged by the detector. Wrap each capture in an RLock/RUnlock, matching the RLock-for-reads convention already used by sendCMCommand and SendInputViaControlMode elsewhere in this file. The snapshotted channel remains safe to select on after unlock — only the read of the struct field itself needed synchronization. Verified with `go test -race ./session/tmux/... -run TestControlModeSurvivesRestart -count=5` (clean, 5/5) and the full `go test -race ./session/tmux/...` suite (clean). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3683CH7Fs9zYR2yP3Dpba
✅ Registry ValidationTest Coverage: 19/179 features have
|
Collaborator
Author
|
Closing — this was accidentally opened against the work repo instead of the personal fork (tstapler/stapler-squad), where the equivalent fix was merged as part of smtg-ai#276. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a genuine Go data race in
session/tmux/control_mode.go, confirmed viago test -raceand currently failing theTestCI job on every PR (pre-existing onmain, newly surfaced now that CI can get past the LFS quota blocker far enough to run-racetests).Root cause
StopControlModewritest.controlModeDone = nil(and closes it) while holdingt.controlModeSubMu.Lock(). Two reader goroutines —readControlModeOutputandmonitorControlModeErrors— each capturedt.controlModeDoneat their first line with a comment acknowledging the hazard ("capture before StopControlMode can nil it"), but neither actually took the lock. The race detector caught themonitorControlModeErrorsinstance:readControlModeOutputhas the identical unsynchronized-read pattern one function up, so it's fixed too for consistency — the race detector just hadn't happened to schedule that interleaving in this run.Fix
Wrap each capture in
controlModeSubMu.RLock()/RUnlock(), matching the existing RLock-for-reads convention already used elsewhere in this file (sendCMCommand,SendInputViaControlMode). The snapshotted channel value remains safe toselecton after unlock — only the read of the shared struct field itself needed synchronization.Testing
go test -race ./session/tmux/... -run TestControlModeSurvivesRestart -count=5— clean, 5/5, no race warnings (this is the regression test from BUG-042, commitb6e76be7d, that was correctly exposing this pre-existing race)go test -race ./session/tmux/...— full package suite, cleango test ./session/tmux/...(no-race) — clean; one test (TestEnsureServerRunning_NoOp) flaked once under full-suite tmux-server contention but passed 3/3 in isolation and on a full-suite re-run, unrelated to this changemake build,make lint— cleanImpact
session/tmux/control_mode.goonly, two reader goroutinesReviewer notes
controlModeDone, consistent with other RLock reads in this file) rather than a fullLock()🤖 Generated with Claude Code