Summary
The existing TestDeniedAskUserQuestionDoesNotStrandRunStatus fixture publishes runID after StartRun has already dispatched the provider goroutine. The provider reads runID on its second completion while the test goroutine may still be assigning it. GitHub Actions race CI caught the data race and an empty sampled status, blocking every merge.
Acceptance criteria
- The test establishes an explicit happens-before edge before the provider reads the run ID.
- The test still samples the run status during the second provider completion, after the denied AskUserQuestion call and before terminal completion.
- It still proves the run is running rather than waiting_for_user and eventually reaches a terminal state.
- Focused normal and race stress, internal/harness normal/race, GitHub CI, and the full repository regression gate pass.
In scope
- Test-fixture synchronization and durable regression coverage for this exact publication race.
- Engineering log, plan, impact map, and plans index.
Out of scope
- Production Runner dispatch ordering, provider behavior, AskUserQuestion permission semantics, or status-state changes.
Current architecture and search evidence
- internal/harness/runner.go StartRun stores the run and dispatches execute in a goroutine before returning.
- internal/harness/tool_gates_regression_test.go assigns runID only after StartRun returns, while funcProvider reads it inside that dispatched goroutine.
- CompletionRequest does not carry a run ID, so the fixture must synchronize publication explicitly.
- Adjacent tool-gate tests and production status ownership do not share this test-local closure state.
Impact analysis
- Harness/API behavior: none; test-only repair.
- TUI/macOS GUI: none; their production status behavior remains covered by the repaired test.
- Config/schema/persistence/providers/security: none after repository search; no runtime files change.
- Concurrency/reliability: explicit one-shot channel handoff removes unsynchronized closure access without serializing production code.
- Compatibility/deployment/observability: no deployment change; CI becomes repeatable. Rollback is reverting the fixture handoff if it weakens the mid-run assertion.
- Documentation: engineering/long-term logs, plan, impact map, and plans index only.
Test-first plan
- Red: GitHub Actions make test-race reports a write at tool_gates_regression_test.go:170 racing the provider read at line 150; the sampled status may be empty.
- Green: use a test-local channel handoff so the provider receives the returned run ID only after StartRun returns.
- Stress: focused normal and race at count=100, then internal/harness normal/race.
- Full: ./scripts/test-regression.sh and GitHub required checks.
Rollout and rollback
Test-only change. Merge after exact-head automated review and green required checks. Revert the test synchronization if it no longer samples the status in step two or if it masks a production-state failure.
Dependencies
Related to epic #1000 and blocks PRs #1043, #1041, #1033, plus the callback/GUI merge chain.
Summary
The existing TestDeniedAskUserQuestionDoesNotStrandRunStatus fixture publishes runID after StartRun has already dispatched the provider goroutine. The provider reads runID on its second completion while the test goroutine may still be assigning it. GitHub Actions race CI caught the data race and an empty sampled status, blocking every merge.
Acceptance criteria
In scope
Out of scope
Current architecture and search evidence
Impact analysis
Test-first plan
Rollout and rollback
Test-only change. Merge after exact-head automated review and green required checks. Revert the test synchronization if it no longer samples the status in step two or if it masks a production-state failure.
Dependencies
Related to epic #1000 and blocks PRs #1043, #1041, #1033, plus the callback/GUI merge chain.