Skip to content

test(orchestration): give the stopping-worker task guard a Run (fixes main shard 3/8) - #19571

Closed
nwparker wants to merge 2 commits into
mainfrom
nwparker/fix-stopping-worker-task-guard-run
Closed

nwparker wants to merge 2 commits into
mainfrom
nwparker/fix-stopping-worker-task-guard-run

Conversation

@nwparker

@nwparker nwparker commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

Two PRs merged to main 16 seconds apart. One made "every Task must belong to a Run" a hard rule. The other added a new test that creates a Task without a Run. Each passed CI on its own, because each was tested against a main that did not yet contain the other. Together they fail: 7 tests in one file throw Run is required, and every PR in the repo has been red on test / tests node 24 3/8 (and therefore verify) since. This PR passes the Run in the new test — 2 lines.

Root cause

Neither author could have seen it. This is not a review-quality failure; the gap is entirely in the merge gate (see below).

Why fix the test and not createTask

Re-loosening createTask would undo an intentional, pinned invariant to make a newer test pass — symptom-patching. The stale side is #19551's test. Both bare calls now pass runId: 'run_legacy_local', which is:

  • the exact shape fix(orchestration): file federated worker mail under the coordinator Run #19542 established across the codebase (it introduced no run fixture; the literal is the pattern), and
  • the run every fresh database already contains: migrate-v2-v12.ts inserts the run_legacy_local row (INSERT OR IGNORE INTO runs) during schema setup, and create-core-tables-sql.ts makes it the column default, so requireRun('run_legacy_local') passes in the :memory: db these tests use.

Sweep: is this the only stale caller?

Yes, measured two ways:

  • CI: on a PR with an unfixed tree (fix(i18n): drop orphan TerminalPane.minimumContrast entries that broke main static analysis #19566, en.json-only change), shards 1, 2, 4–8 were all green; shard 3/8 was the sole casualty, and its 7 failures are all this file.
  • Local, with this fix: pnpm test over src/main/runtime/orchestration + src/main/runtime/rpc/methods/orchestration → 194 files, 1611 tests, 0 failures. A brace-aware scan found 106 other bare createTask({...}) calls; all are benign — 1 is the intentional pin in writer-run-required.test.ts, the other 105 are RPC-harness tests where rpc-test-harness.ts:47 wraps db.createTask to inject runId ?? activeRunId.

Evidence this is a main break (measured)

Systemic: three gaps that compound, not one

Two ungated breaks reached main inside 24 minutes:

  1. 07:39Z — feat(terminal): make the contrast floor user-configurable (#10754) #18126 merged with its static analysis job cancelled (0 required checks reached success on its head), leaving 7 orphan i18n entries that fail verify:localization-runtime-catalog for every PR (fixed separately in fix(i18n): drop orphan TerminalPane.minimumContrast entries that broke main static analysis #19566).
  2. 08:03Z — fix(orchestration): file federated worker mail under the coordinator Run #19542 and fix(orchestration): refuse Task re-open under a live worker; allow stop re-issue on a stranded row #19551 merged 16 seconds apart. The second break would have been caught by the very check the first one skipped.

Neither author could have seen it. This is not a review-quality failure. The full cause is three gaps in the merge gate that compound, in this order:

  1. cancelled ≠ failed. A cancelled required check reads as "not failing," so feat(terminal): make the contrast floor user-configurable (#10754) #18126 merged ungated. Cancelled must be treated as not verified.
  2. No up-to-date-base requirement. "Green alone" was treated as "green together": each of fix(orchestration): file federated worker mail under the coordinator Run #19542/fix(orchestration): refuse Task re-open under a live worker; allow stop re-issue on a stranded row #19551 was validated against a main that did not contain the other, and nothing forced re-validation against the base they actually landed on.
  3. No post-merge signal on main. pr.yml triggers on pull_request only, and unit-tests.yml / e2e.yml are workflow_call. main gets no CI on push, so neither break was ever visible on main itself — both were observable only as red on other people's PRs. That is why this went hours undetected and why several agents each rediscovered it while debugging their own PR.

The first two are fixed by branch protection with strict status checks (required checks + up-to-date base) — which main currently cannot have, because it has no branch protection and no rulesets at all (both APIs return empty/404). The third needs a push trigger on main.

Why a standalone PR rather than riding along in #19544 / #19337

Both of those PRs currently carry an identical copy of this 2-line change bundled into unrelated feature work (#19544 is +1014/−1592 across 74 files; #19337 is 23 files). Landing it on its own is deliberate:

  1. Reviewability. Nobody reviewing a 74-file feature PR is reviewing this fix; it rides along invisibly. A standalone PR is the unit a reviewer can actually judge.
  2. Revertability. If Remove settled-worker automatic resume and hibernation fences #19544 has to be reverted for its feature content, main silently goes red again — the fix leaves with the feature. An independent commit survives that.
  3. It does not block on their timeline. fix(orchestration): fence worker release on mobile keystrokes #19337's static analysis is cancelled on its current head — the fourth cancelled gate today. Waiting on those PRs means waiting on the exact failure mode this write-up is about.

If either of theirs merges first, this PR becomes a clean no-op on this file and closes.

This PR's own first CI run demonstrated gap 2. It was opened at 08:45:33Z; #19566 merged at 08:45:52Z — 19 seconds later. GitHub snapshots the PR base at trigger time, so the first run validated e958ae5 against the pre-#19566 main (d346d6f) and failed the runtime-catalog step that #19566 had just fixed. A plain merge of origin/main re-ran it against the base it will actually land on. That is exactly the re-validation an up-to-date-base requirement forces.

Verification

  • pnpm test src/main/runtime/orchestration/db-stopping-worker-task-guard.test.ts → 7 passed.
  • Orchestration-wide sweep above → 194 files / 1611 tests pass.
  • oxlint and oxfmt --check clean on the file. Diff is exactly the two runId: additions.

#19542 tightened createTask so a Task without a Run throws instead of
falling back to the legacy Run, and migrated every existing test to pass
`runId: 'run_legacy_local'` (254 such lines) with writer-run-required.test.ts
pinning the throw. #19551 added this file 16 seconds later, validated
against a main that predated #19542, with two bare createTask calls. Each
PR was green alone; together shard 3/8 fails all 7 cases here with
"Run is required".

The stale side is this test, not createTask: re-loosening the writer would
undo a deliberate, pinned invariant to make a newer test pass. Pass the
legacy Run at both sites, matching the shape #19542 established.

Swept the rest: with this change, every test under src/main/runtime/
orchestration and src/main/runtime/rpc/methods/orchestration passes
(194 files, 1611 tests); the remaining bare createTask calls are either
the intentional pin in writer-run-required.test.ts or RPC-harness tests
whose db is bound to a run by the harness.
@coderabbitai

coderabbitai Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 9c51f340-283a-4635-af8f-c61bfd50fab8

📥 Commits

Reviewing files that changed from the base of the PR and between d346d6f and e958ae5.

📒 Files selected for processing (1)
  • src/main/runtime/orchestration/db-stopping-worker-task-guard.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

The supervised-worker and unsupervised-worker task fixtures now set runId to 'run_legacy_local'. Test behavior remains unchanged.

Merge Risk: ⚪ Minimal · up to 19576

The stopping-worker task fixtures now attach created tasks to the legacy local run, preserving the existing test scenarios while satisfying the required task-run relationship. No production behavior changes, and current merge-readiness risk is minimal.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description explains the root cause, rationale, scope, and verification in detail. However, it omits required template sections, including a valid Linked Issue, Visual Proof with an N/A explanatio… Complete the required template sections. Add a valid Fixes #<issue> reference, add Visual Proof with N/A and a reason, move verification details into Testing, and complete or explicitly mark the remaining sections as applicable or n…
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the orchestration test fix and the affected shard. It is concise and related to the main change.
Full details: Description check

Explanation

The description explains the root cause, rationale, scope, and verification in detail. However, it omits required template sections, including a valid Linked Issue, Visual Proof with an N/A explanation, Testing, AI Disclosure, Review, Agent skill upstream boundary, Notes, and Checklist.

Resolution

Complete the required template sections. Add a valid Fixes #&lt;issue&gt; reference, add Visual Proof with N/A and a reason, move verification details into Testing, and complete or explicitly mark the remaining sections as applicable or not applicable.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • Test guard given a Run — adds runId: 'run_legacy_local' to the two db.createTask calls in db-stopping-worker-task-guard.test.ts, restoring them after #19542 made runId mandatory.

Verified the fix is correct and consistent: run_legacy_local is LEGACY_RUN_ID and is seeded into runs on every fresh database (INSERT OR IGNORE INTO runs in db/schema/migrate-v2-v12.ts), so requireRun('run_legacy_local') passes in the :memory: db these tests use. The literal form also matches the dominant pattern across the orchestration test suite. Ran pnpm test src/main/runtime/orchestration/db-stopping-worker-task-guard.test.ts — 7 passed.

The PR body's root-cause and systemic write-up (cancelled checks read as green, no up-to-date-base requirement, no push-triggered CI on main) is well-documented and worth carrying into any follow-up on the merge gate itself, but it is outside this diff's scope.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏


function localWorker() {
const task = db.createTask({ spec: 'local work' })
const task = db.createTask({ runId: 'run_legacy_local', spec: 'local work' })

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — correct, and the PR body is fixed: create-core-tables-sql.ts only supplies the column DEFAULT; the run_legacy_local row itself is inserted by migrate-v2-v12.ts (INSERT OR IGNORE INTO runs) during schema setup, which is why requireRun passes on a fresh :memory: db.

@nwparker

nwparker commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Closing as a no-op: #19544 merged the identical two hunks (commit 12f53da), so the diff against main on this test file is now empty. Merging this would produce zero content change.

The diagnosis stands and is recorded here for reference: #19542 tightened createTask to throw "Run is required", #19551 landed 16 seconds later with a fixture that called it without a runId, and each was green alone because neither was validated against the other's merged base.

The gap is the merge gate, not review quality — main has no branch protection, no strict status checks, and no CI on push, so nothing re-validated either PR against the updated base and neither break ever showed red on main itself. Both authors were working from green signals.

@nwparker nwparker closed this Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant