Repository navigation
fix(bench): kill a TTY exchange on inactivity instead of total time - #3997
Alan-TheGentleman merged 2 commits into
Conversation
j121-rdd-tui-controls-global-mode fails intermittently in the CI
transition-axis run while passing the core run minutes earlier (#3971):
under that run's CPU contention the TUI keeps painting, but the whole
four-screen exchange outlives the fixed 10s deadline that
context.WithTimeout applied to the entire runTTY call, so the child is
killed mid-read ("read TUI before ...: input/output error; context
deadline exceeded; signal: killed").
Of the two shapes the issue admits — a load-reflecting deadline or a
read that tolerates the slower first frame — this takes the second: a
ttyWatchdog now expires the exchange only after ttyTimeout (still 10s)
WITHOUT receiving a single PTY byte, and every received byte resets that
timer, so a slow, trickling frame under load survives while a hung TUI
still dies after exactly the old budget. A new generous
ttyOverallTimeout (2min) caps the whole exchange so a TUI that paints
forever without reaching the expected screen still terminates
deterministically. Both causes unwrap to context.DeadlineExceeded, so
callers classify them exactly as before. A blind bump of the total
budget was rejected because it would slow every genuinely hung exchange
by the same factor without removing the load sensitivity.
TestRunTTYToleratesASlowFirstFrameUnderLoad is the CI failure scaled
down: a scripted terminal keeps making progress (every silence shorter
than the budget) but completes the j121 banner only after more than the
budget in total, read through j121's own waitForReviewModeTTY helper.
On the previous commit it fails with the CI shape ("read TUI before
\"RDD is currently ENABLED globally.\" ... context deadline exceeded");
with this change it passes. TestRunTTYOverallCapKillsAForeverChattering-
Exchange and TestRunTTYInactivityBudgetStillKillsASilentExchange pin
the two new budgets without real 10s waits.
Native-Windows bar: the bench module's failing-test set is byte-for-byte
identical before and after this change (the #3934 pair plus the ten
pre-existing fixture failures matching #2603); this commit fixes none of
them and introduces no new failure. The driven journey corpus itself is
deferred to CI.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Team Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughChangesThe TTY runner now enforces separate inactivity and overall exchange deadlines. PTY byte reads refresh the inactivity deadline. Tests cover slow, continuous, and silent output. The issue TTY exchange watchdog
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The PR changes benchmark TTY timeout handling to tolerate active but slow output while retaining inactivity and overall limits; no actionable merge-blocking risk remains after normal checks and review. Sequence Diagram(s)sequenceDiagram
participant runTTYWithDeadlines
participant ttyProgressReader
participant ttyWatchdog
runTTYWithDeadlines->>ttyWatchdog: Create inactivity and overall budgets
ttyProgressReader->>ttyWatchdog: Record each PTY byte
ttyWatchdog->>runTTYWithDeadlines: Cancel with timeout cause
runTTYWithDeadlines->>ttyWatchdog: Read context cancellation cause
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Linked Issues checkExplanation The changes satisfy issue [ Full details: Out of Scope Changes checkExplanation All changes are related to the linked issue [
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…side Empty commit; branch content unchanged at fbe6c03 base + cherry-picks. Targeted packages verified locally: internal/components/sdd (74.6s), internal/assets (50.5s), internal/reviewtransaction (212.1s) all green. The previous Windows Runtime failure on TestRepairClassifiedAuthority ConcurrentExecutionCommitsAndReplays is currently red on Alan's own in-flight PRs (Gentleman-Programming#3997, Gentleman-Programming#3957) which do not modify reviewtransaction, so this push is to rule out a diff-local regression on a healthy runner.
…Available modal cannot cover the exchange
The watchdog diagnostic from the previous commit exposed a second
manifestation of #3971, distinct from the load-slowness that commit
addresses: the bench sandbox gives each journey a fresh HOME with no
state.json, so CheckAllWithCooldown (internal/update/cooldown.go) finds
no LastUpdateCheck and runs the launch update check. When upstream main
is newer than the CI-built binary, the TUI shows the Update Available
modal (internal/tui/screens/update_prompt.go), which covers the menu
j121's exchange waits for ("Start installation" never arrives), and
after 10s of true silence the watchdog kills the run.
Seed j121's sandbox HOME with a state.json carrying a current
last_update_check before the TTY exchange, mirroring the precedent in
bench/journeys_issue_3561.go (green in CI for the same reason). No
state.json exists at that point and the journey runs against a
not-installed state, so only the cooldown field is seeded.
|
The Unit Tests red on commit 1 (7a5b914) is expected and diagnostic, not a regression. #3971 turns out to have two distinct manifestations in the same fragile journey. The original occurrences are load slowness mid-exchange, which commit 1's inactivity watchdog addresses. Running under that watchdog, CI then exposed the second: the bench sandbox gives every journey a fresh HOME with no update-check cooldown, so the launch update check runs, and when upstream Commit 2 (662aaf8, +15/−0, j121's journey file only) seeds a current |
20d7574
into
Gentleman-Programming:main
#3023) * fix(opencode): keep SDD phase commands in primary orchestrator session OpenCode treats `subtask: true` in command YAML frontmatter as a forced sub-agent invocation, overriding the `gentle-orchestrator` `mode: primary` setting in `internal/assets/opencode/sdd-overlay-single.json`. The five SDD phase commands (`sdd-init`, `sdd-explore`, `sdd-apply`, `sdd-verify`, `sdd-archive`) all carried that flag, producing a redundant nested hop. The orchestrator's `permission.task` allowlist already lists every phase worker, so removing `subtask: true` only changes session topology — not delegation capability — and lets the orchestrator stay primary. Closes #2939 Source scope (5 files, 1 line removed each): - internal/assets/opencode/commands/sdd-init.md - internal/assets/opencode/commands/sdd-explore.md - internal/assets/opencode/commands/sdd-apply.md - internal/assets/opencode/commands/sdd-verify.md - internal/assets/opencode/commands/sdd-archive.md Golden regeneration (2 files, 1 line removed each): - testdata/golden/sdd-opencode-cmd-sdd-init.golden - testdata/golden/sdd-opencode-cmd-sdd-apply.golden New regression test: - internal/components/sdd_opencode_subtask_test.go -- byte-level assertion that none of the 5 source files reintroduce `subtask: true` Out of scope (intentional, per user decision 2026-08-11): - internal/assets/opencode/commands/sdd-onboard.md -- also carries `subtask: true` but the issue enumerates it as a negative control. Follow-up issue to be filed separately. - skill-creator.md / skill-registry.md -- different domain. - All non-OpenCode adapters -- none use per-command `subtask`. * fix(opencode): include sdd-onboard in the subtask-removal regression per decode2 - Remove the `subtask: true` line from internal/assets/opencode/commands/sdd-onboard.md. The other five SDD phase commands were already corrected in the parent commit; sdd-onboard was missed because it is the only one whose first paragraph mentions the hidden "sdd-onboard" sub-agent (rather than the more obvious sdd-apply/sdd-archive pair) and was easy to overlook in the original sweep. The yaml frontmatter now matches the other six phase commands. - Extend TestNoSubtaskOnSDDOpenCodeCommands to enumerate all six frontmatter files (sdd-init, sdd-explore, sdd-apply, sdd-verify, sdd-archive, sdd-onboard). The static byte-level check now catches a regression on any of them, and the docstring and comments reflect the six-command reality. - Installation/injection coverage: inject.go's compatibilitySDDSkillIDs and claudeModelAssignmentRowOrder already include sdd-onboard; no change needed there. * test(opencode): prove installed SDD commands stay parent-owned Completes the coverage decode2 requested on PR #3023 for #2939: the existing guard only scanned the source assets, so an injection path that re-introduced `subtask: true` would have passed unnoticed. - TestInjectOpenCodeSDDCommandsRemainParentOwned runs Inject for the OpenCode adapter and asserts each installed delegating SDD command keeps `agent: gentle-orchestrator` and carries no `subtask: true` in its installed frontmatter. - TestNoSubtaskOnSDDOpenCodeCommands now scopes its assertion to the YAML frontmatter block instead of the whole file, so prose documenting the field cannot fail the source ratchet. - Both guards also pin that removing the field never detaches the command from gentle-orchestrator routing. RED/GREEN: re-adding `subtask: true` to any listed asset fails both tests; removing it passes. go test ./internal/components/... green, gofmtcheck green, e2e 3/3 platforms. * fix(opencode): keep sdd-research parent-owned sdd-research joined the OpenCode command set after the #2939 approval (74cc41b) with the exact shape the issue fixed: `agent: gentle-orchestrator` plus `subtask: true`, which forces the primary orchestrator into a sub-agent invocation before the hidden sdd-research phase worker can launch. Same root correction as the approved six: remove only the field, keep the routing. Both ratchets (source assets and installed commands) now enumerate it, and its golden is regenerated. Kept as a separate work unit so maintainers can drop the scope expansion with a single revert if they prefer the strictly approved six-command set. * test(opencode): decode frontmatter YAML in the parent-owned ratchets Addresses the CodeRabbit finding on #3864: substring-matching the raw frontmatter block could false-pass or false-fail when a description or comment mentions the literal `agent:` or `subtask:` values. Both ratchets now unmarshal the frontmatter into a typed struct and assert the exact agent value and the effective subtask flag. Verified: - RED: re-adding `subtask: true` to an asset fails both ratchets. - False-pass guard: a YAML comment (`# subtask: true`) and a quoted description containing the literal both pass with the field absent, and invalid frontmatter fails loudly at decode. - go test ./internal/components/ ./internal/components/sdd/ green. * style(opencode): sort yaml import in the injection ratchet gofmt requires the gopkg.in/yaml.v3 import at the end of the sorted block; the previous commit inserted it mid-block and every CI job gates on Go Format passing. * test(opencode): cover the upgrade and repeat-install paths in the ratchet Addresses the CodeRabbit finding on #3864: the ratchet ran Inject once, proving only the fresh-install path. It now also covers: - Upgrade: a pre-fix install whose sdd-init.md still declares `subtask: true` must be overwritten by a re-sync, never preserved, so users upgrading from an affected version reach the fixed state. - Repeat install: a second Inject must report no changes and leave the parent-owned metadata in place. Assertions extracted to assertSDDCommandsParentOwned so both install passes share the exact same metadata checks. * ci(retouch): retrigger CI to confirm Windows Runtime flake is runner-side Empty commit; branch content unchanged at fbe6c03 base + cherry-picks. Targeted packages verified locally: internal/components/sdd (74.6s), internal/assets (50.5s), internal/reviewtransaction (212.1s) all green. The previous Windows Runtime failure on TestRepairClassifiedAuthority ConcurrentExecutionCommitsAndReplays is currently red on Alan's own in-flight PRs (#3997, #3957) which do not modify reviewtransaction, so this push is to rule out a diff-local regression on a healthy runner. --------- Co-authored-by: danielgap <soydanielgap@gmail.com> Co-authored-by: ardelperal <ardelperal@users.noreply.github.com>
fix(bench): kill a TTY exchange on inactivity instead of total time
#3023) * fix(opencode): keep SDD phase commands in primary orchestrator session OpenCode treats `subtask: true` in command YAML frontmatter as a forced sub-agent invocation, overriding the `gentle-orchestrator` `mode: primary` setting in `internal/assets/opencode/sdd-overlay-single.json`. The five SDD phase commands (`sdd-init`, `sdd-explore`, `sdd-apply`, `sdd-verify`, `sdd-archive`) all carried that flag, producing a redundant nested hop. The orchestrator's `permission.task` allowlist already lists every phase worker, so removing `subtask: true` only changes session topology — not delegation capability — and lets the orchestrator stay primary. Closes #2939 Source scope (5 files, 1 line removed each): - internal/assets/opencode/commands/sdd-init.md - internal/assets/opencode/commands/sdd-explore.md - internal/assets/opencode/commands/sdd-apply.md - internal/assets/opencode/commands/sdd-verify.md - internal/assets/opencode/commands/sdd-archive.md Golden regeneration (2 files, 1 line removed each): - testdata/golden/sdd-opencode-cmd-sdd-init.golden - testdata/golden/sdd-opencode-cmd-sdd-apply.golden New regression test: - internal/components/sdd_opencode_subtask_test.go -- byte-level assertion that none of the 5 source files reintroduce `subtask: true` Out of scope (intentional, per user decision 2026-08-11): - internal/assets/opencode/commands/sdd-onboard.md -- also carries `subtask: true` but the issue enumerates it as a negative control. Follow-up issue to be filed separately. - skill-creator.md / skill-registry.md -- different domain. - All non-OpenCode adapters -- none use per-command `subtask`. * fix(opencode): include sdd-onboard in the subtask-removal regression per decode2 - Remove the `subtask: true` line from internal/assets/opencode/commands/sdd-onboard.md. The other five SDD phase commands were already corrected in the parent commit; sdd-onboard was missed because it is the only one whose first paragraph mentions the hidden "sdd-onboard" sub-agent (rather than the more obvious sdd-apply/sdd-archive pair) and was easy to overlook in the original sweep. The yaml frontmatter now matches the other six phase commands. - Extend TestNoSubtaskOnSDDOpenCodeCommands to enumerate all six frontmatter files (sdd-init, sdd-explore, sdd-apply, sdd-verify, sdd-archive, sdd-onboard). The static byte-level check now catches a regression on any of them, and the docstring and comments reflect the six-command reality. - Installation/injection coverage: inject.go's compatibilitySDDSkillIDs and claudeModelAssignmentRowOrder already include sdd-onboard; no change needed there. * test(opencode): prove installed SDD commands stay parent-owned Completes the coverage decode2 requested on PR #3023 for #2939: the existing guard only scanned the source assets, so an injection path that re-introduced `subtask: true` would have passed unnoticed. - TestInjectOpenCodeSDDCommandsRemainParentOwned runs Inject for the OpenCode adapter and asserts each installed delegating SDD command keeps `agent: gentle-orchestrator` and carries no `subtask: true` in its installed frontmatter. - TestNoSubtaskOnSDDOpenCodeCommands now scopes its assertion to the YAML frontmatter block instead of the whole file, so prose documenting the field cannot fail the source ratchet. - Both guards also pin that removing the field never detaches the command from gentle-orchestrator routing. RED/GREEN: re-adding `subtask: true` to any listed asset fails both tests; removing it passes. go test ./internal/components/... green, gofmtcheck green, e2e 3/3 platforms. * fix(opencode): keep sdd-research parent-owned sdd-research joined the OpenCode command set after the #2939 approval (74cc41b) with the exact shape the issue fixed: `agent: gentle-orchestrator` plus `subtask: true`, which forces the primary orchestrator into a sub-agent invocation before the hidden sdd-research phase worker can launch. Same root correction as the approved six: remove only the field, keep the routing. Both ratchets (source assets and installed commands) now enumerate it, and its golden is regenerated. Kept as a separate work unit so maintainers can drop the scope expansion with a single revert if they prefer the strictly approved six-command set. * test(opencode): decode frontmatter YAML in the parent-owned ratchets Addresses the CodeRabbit finding on #3864: substring-matching the raw frontmatter block could false-pass or false-fail when a description or comment mentions the literal `agent:` or `subtask:` values. Both ratchets now unmarshal the frontmatter into a typed struct and assert the exact agent value and the effective subtask flag. Verified: - RED: re-adding `subtask: true` to an asset fails both ratchets. - False-pass guard: a YAML comment (`# subtask: true`) and a quoted description containing the literal both pass with the field absent, and invalid frontmatter fails loudly at decode. - go test ./internal/components/ ./internal/components/sdd/ green. * style(opencode): sort yaml import in the injection ratchet gofmt requires the gopkg.in/yaml.v3 import at the end of the sorted block; the previous commit inserted it mid-block and every CI job gates on Go Format passing. * test(opencode): cover the upgrade and repeat-install paths in the ratchet Addresses the CodeRabbit finding on #3864: the ratchet ran Inject once, proving only the fresh-install path. It now also covers: - Upgrade: a pre-fix install whose sdd-init.md still declares `subtask: true` must be overwritten by a re-sync, never preserved, so users upgrading from an affected version reach the fixed state. - Repeat install: a second Inject must report no changes and leave the parent-owned metadata in place. Assertions extracted to assertSDDCommandsParentOwned so both install passes share the exact same metadata checks. * ci(retouch): retrigger CI to confirm Windows Runtime flake is runner-side Empty commit; branch content unchanged at fbe6c03 base + cherry-picks. Targeted packages verified locally: internal/components/sdd (74.6s), internal/assets (50.5s), internal/reviewtransaction (212.1s) all green. The previous Windows Runtime failure on TestRepairClassifiedAuthority ConcurrentExecutionCommitsAndReplays is currently red on Alan's own in-flight PRs (#3997, #3957) which do not modify reviewtransaction, so this push is to rule out a diff-local regression on a healthy runner. --------- Co-authored-by: danielgap <soydanielgap@gmail.com> Co-authored-by: ardelperal <ardelperal@users.noreply.github.com>
🔗 Linked Issue
Closes #3971
🏷️ PR Type
What kind of change does this PR introduce?
type:bug— Bug fix (non-breaking change that fixes an issue)type:feature— New feature (non-breaking change that adds functionality)type:docs— Documentation onlytype:refactor— Code refactoring (no functional changes)type:chore— Build, CI, or tooling changestype:breaking-change— Breaking change (fix or feature that changes existing behavior)📝 Summary
On Linux
/dev/ptmxunder transition-axis load (#3971),waitForReviewModeTTYkills an exchange on a fixed total deadline even while the TUI banner is still actively trickling in, producing the CI failureread TUI before "RDD is currently ENABLED globally." ... context deadline exceeded.This PR replaces the total-time budget with an inactivity watchdog: the exchange dies only after
ttyTimeout(10s) without receiving a single PTY byte, and every received byte resets the timer. A newttyOverallTimeout(2min) caps the whole exchange so a forever-chattering terminal still dies. Both kill causes unwrap tocontext.DeadlineExceeded, so caller classification is unchanged.A blind total-budget bump was considered and rejected (rationale recorded in the commit body): any fixed total still loses under enough load, and it slows detection of every genuine hang.
📂 Changes
bench/runner.gottyTimeout, reset on every byte) plus an overall exchange cap (ttyOverallTimeout); both unwrap tocontext.DeadlineExceededbench/runner_test.gowaitForReviewModeTTY(RED on merge base, GREEN on head), plus two tests pinning the overall cap and the inactivity budget🤖 AI Assistance
Select exactly one option. Do not check both options.
Tool/model (if known): Claude Code (Fable)
Material scope: Implementation drafting, test authoring, and verification orchestration, under the contributor's direction and review.
Verification performed: Regression test observed failing on the merge base (1863432) with the CI failure shape and passing on the head (including
-count=5);gofmt -lclean;go run ./internal/gofmtcheckOK;go vet ./...(bench module) OK;go build ./...(root module) OK;git diff --checkclean; bench full-module failing set compared byte-identical before/after the change.Trivial formatting, spelling, minor autocomplete, search/navigation, and trivial, non-substantive mechanical transformations do not need to be itemized. See AI_POLICY.md for the canonical policy.
🧪 Test Plan
Unit Tests
go test ./...Run in the
benchmodule. New tests:TestRunTTYToleratesASlowFirstFrameUnderLoad— scripted terminal replays the j121 banner in trickling frames (silences of 400/400/700ms under a 1s stand-in budget, 1.5s total, over it), read through the realwaitForReviewModeTTY. FAILS on merge base 1863432 with the CI failure shape; PASSES on head, also at-count=5.TestRunTTYOverallCapKillsAForeverChatteringExchangeandTestRunTTYInactivityBudgetStillKillsASilentExchange— pin the overall cap and the inactivity budget.The bench full-module failing set is byte-identical before and after this change: 12 pre-existing failures (the #3934 ConPTY pair plus ten POSIX-fixture failures matching #2603). This PR fixes none of them and adds none. #3934 (native Windows ConPTY) is a different defect from #3971 (Linux
/dev/ptmxunder load) in the same runner; it is used here only as a cross-lane non-regression bar.Go Format
OK (
gofmt -lalso clean).E2E Tests (Docker required)
NOT RUN locally: Docker is unavailable in the local environment; deferred to CI. The change is confined to the
benchmodule.Benchmark Validation
This changes the benchmark runner, so benchmark validation applies. Unit-level validation is covered by the three tests above. The full driven-journey corpus run is NOT RUN locally (it needs the product binary and a long transition-axis run) and is deferred to CI.
Also run locally:
go vet ./...(bench module) OK,go build ./...(root module) OK,git diff --checkclean.go test ./...) — new/changed tests pass; the 12 pre-existing bench failures are unchanged byte-identical before/after (see above)go run ./internal/gofmtcheck)cd e2e && ./docker-test.sh) — deferred to CI (Docker unavailable locally)🤖 Automated Checks
The following checks run automatically on this PR:
additions + deletions) or usesize:exceptionCloses/Fixes/Resolves #Nstatus:approvedtype:*Labeltype:*label must be appliedgo test ./...must passgo run ./internal/gofmtcheckmust passcd e2e && ./docker-test.shmust pass✅ Contributor Checklist
status:approvedsize:exceptionwith rationale documentedtype:*label to this PRgo test ./...)go run ./internal/gofmtcheck)cd e2e && ./docker-test.sh) — deferred to CI (see Test Plan)Co-Authored-Bytrailers💬 Notes for Reviewers
/dev/ptmxunder transition-axis load) and bug(bench): rc.3 PTY runner fails two deterministic native Windows tests #3934 (native Windows ConPTY) are different defects in the same runner. This PR addresses only bug(bench): j121-rdd-tui-controls-global-mode flakes with a pty deadline in the CI transition-axis run #3971; bug(bench): rc.3 PTY runner fails two deterministic native Windows tests #3934 appears here only as a non-regression bar (its failing pair is byte-identical before/after).context.DeadlineExceeded, so existing caller classification and error handling are untouched.For production Go changes in
internal/cli,internal/reviewtransaction, orinternal/sddstatus:Not applicable — this PR changes only the
benchmodule; no qualifying guard population is touched.guard:populationdirection and claim are adjacent and accurate, and that.guard-population-baseline.txtchanged only when the guard contract intentionally changed.Summary by CodeRabbit
Bug Fixes
Tests