Skip to content

test(ci): give the Windows nested live-lock case its own lock owner - #5078

Merged
lidge-jun merged 3 commits into
devfrom
codex/4991-nested-live-lock-owner
Sep 19, 2026
Merged

lidge-jun merged 3 commits into
devfrom
codex/4991-nested-live-lock-owner

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Summary

The nested live-lock regression in tests/ci-workflows/test-runner.test.ts registered only when
process.platform === "win32" && process.env[TEST_RUN_NO_QUEUE_ENV] !== "1". The hosted Windows
batch leg sets OCX_TEST_NO_QUEUE: "1" on its Test in fresh-process batches step, so the case
was skipped on the only platform it applies to. Nested Windows lock inheritance, member creation,
preload and home-guard propagation, and incomplete-capability refusal were all unverified.

The step-wide no-queue setting from #4876 is unchanged. The case now brings its own lock holder, so
it no longer depends on the outer lane having one.

  • The Windows condition stays; only the no-queue exclusion is removed, so the case registers on the
    hosted Windows lane.
  • A new controller, tests/helpers/nested-test-run-lock-controller.ts, runs as a plain bun <file>
    child under an isolated mkdtemp root. Only the environment copy handed to it has the no-queue
    opt-out removed; the outer process environment, the real home, and any pre-existing owner are
    never modified. Its cwd is outside the repository so Bun loads no bunfig preload into the holder
    itself, which would otherwise take the same lock and then wait on itself.
  • The controller resolves the user-scoped lock through resolveWrappedTestRunLockPath and takes it
    with acquireTestRunLock for its own run id. No owner file is written by hand. When a wrapped or
    bare Windows run has already published a complete capability, it joins that owner rather than
    creating a second one for the same path, and then releases nothing.
  • It publishes the capability into its own environment and spawns nested Bun children that inherit
    it. The healthy child returns booleans only: path match, run match, token match, member receipt,
    preload executed, home guard armed. OCX_TEST_PRELOAD_PID and OCX_TEST_HOME_GUARD are stripped
    from every child environment so those two receipts have to come from the child's own preload
    rather than from inheritance.
  • Three children are rejected before they execute: no token (capability is incomplete), a foreign
    token (exact live owner no longer matches), and a foreign lock path (refusing inherited lock access). Each is checked for a non-zero exit, the matching refusal on stderr, and the absence of
    a receipt on stdout.
  • A fifth scenario plants a live foreign owner under the temporary root and proves the acquire path
    waits it out and gives up rather than reclaiming it, leaving that owner byte-identical. The
    elapsed floor is asserted, so an immediate refusal carrying the right message does not pass.
  • The owner receipt is compared byte for byte after every scenario. In finally the controller
    requires the full spawn count to have been waited on, then releases only a lock it acquired and
    re-reads the planted foreign owner afterwards. There is no broad kill.
  • Output is one JSON line of booleans plus diagnostics with every UUID-shaped substring removed;
    receiptRedacted scans the serialized body for one rather than trusting the token it happens to
    know. Child stdout is parsed, never echoed.

Two deadlines, and where the child budget lives

The controller is handed an absolute deadline 10s below the caller's spawnSync timeout and bounds
each child by what is left of it minus a cleanup reserve. Without that, four child ceilings could
outlast the 45s hard kill on a failure path, terminating the controller inside a spawn with the lock
still held and its teardown never reached. The spawnSync timeout remains the backstop, and no
budget was widened.

The nominal per-child timeout is declared by the test and passed to the controller rather than
re-derived inside it. That keeps the deadline bounding four cold Bun starts with the case that owns
them, and keeps tests/ci-workflows/cold-spawn-warmup.test.ts seeing this file: an earlier revision
moved the constant into the helper, which orphaned this file's disposition and failed test 3/4 and
windows 7/9.

Closes #4991

Verification

No local suite was run. No local bun test, focused test, typecheck, build, install, or ocx
invocation was executed in this checkout. Verification is static reasoning plus hosted CI.

  • platform-windows is workflow_dispatch-only, so it was dispatched against this branch. On
    hosted windows-latest shard 6/9 of run 35380618954, at head 499ca44f27, with
    OCX_TEST_NO_QUEUE=1 actually set on the batch step, the case is recorded as executed rather than
    skipped:

    (pass) bun test user lock > a nested Windows Bun test inherits the live lock its controller
    holds and refuses an incomplete capability [4035.04ms]
    

    Both the healthy receipt and the three rejection receipts are inside that assertion: the case
    fails unless all twelve controller booleans are true and diagnostics are empty. All nine Windows
    shards are green at that head.

  • test 1/4..4/4, macos 1/2..2/2, gates, storage policy, api usage, docker smoke, keyring,
    structure gate, npm-global, hygiene and enforce-target are green at the same head.

  • No new *.test.ts file was added, so scripts/test-layout/layout.json and
    tests/fixtures/test-layout-expected.json need no entry; tests/test-layout.test.ts indexes only
    *.test.ts and treats tests/helpers/ as support-only.

  • File-size ratchet: neither touched file has a cap in tests/fixtures/file-size-baseline.json, and
    both are well under the 2000-line threshold.

Known limits

  • On the no-queue lane the controller creates and ACL-hardens the real per-user
    ...\bun-test-locks directory, because that path is what resolveInheritedTestRunLock validates
    by host digest and basename. A relocated lock would be rejected by the very check under test. A
    wrapped or bare Windows run already holds that lock, so the acquire branch is reached only when
    no-queue is set.
  • reclaimStaleLock in scripts/test-run-lock.ts has a pre-existing check-then-rename race between
    two contenders. This change adds one contender on a lane that has no other, and closing the race
    would mean editing the lock implementation, which is outside this fix.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

The nested live-lock regression registered only when OCX_TEST_NO_QUEUE was
not 1, and the hosted Windows batch leg sets exactly that, so the case was
skipped on the only platform it applies to.

A controller child now owns the lock instead of borrowing the lane's: it runs
with the opt-out removed for itself alone, resolves the user-scoped path
through the ordinary safe path, acquires it for its own run id, and spawns the
nested Bun children that must inherit it. The outer environment, the real
home, and any pre-existing owner are left untouched.

Closes #4991
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 18, 2026 18:07
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 18, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-18T18:10:46.438929Z 5a97902 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the chore Maintenance, CI, tests, refactors, or build changes (not a user-facing bug or feature). label Sep 18, 2026
@coderabbitai

coderabbitai Bot commented Sep 18, 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 8b13246f-3583-47ae-ab43-b502c7a84755

📥 Commits

Reviewing files that changed from the base of the PR and between 6044a9a and 499ca44.

📒 Files selected for processing (2)
  • tests/ci-workflows/test-runner.test.ts
  • tests/helpers/nested-test-run-lock-controller.ts

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


📝 Walkthrough

Walkthrough

The change adds a Windows-only controller for nested live-lock validation. The runner starts it outside the repository, verifies healthy and refused child cases, checks foreign-owner protection, and confirms parent environment isolation.

Changes

Nested live-lock regression

Layer / File(s) Summary
Controller lock setup
tests/helpers/nested-test-run-lock-controller.ts
Defines the receipt contract and controller entry point. Validates Windows execution, deadlines, temporary paths, spawn options, and no-queue settings. Joins an inherited lock or acquires a new lock.
Nested child validation
tests/helpers/nested-test-run-lock-controller.ts
Creates a temporary Bun test fixture. Runs healthy, tokenless, foreign-token, and foreign-path children. Checks inherited capabilities, refusal errors, and owner-file stability.
Ownership isolation and receipt
tests/helpers/nested-test-run-lock-controller.ts
Tests timeout behavior for a foreign owner. Verifies child cleanup and ownership-specific release behavior. Redacts UUIDs and emits the final JSON receipt.
Windows runner integration
tests/ci-workflows/test-runner.test.ts, tests/helpers/repo-root.ts
Runs the regression without the outer no-queue setting. Starts the controller with bounded deadlines, validates all receipt flags and exit status, and confirms the parent environment remains unchanged.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Other · Severity of issue fixed: Low

Sequence Diagram(s)

sequenceDiagram
  participant WindowsRunner
  participant NestedLiveLockController
  participant BunTestChild
  participant TestRunLock
  WindowsRunner->>NestedLiveLockController: spawn with temp root and deadline
  NestedLiveLockController->>TestRunLock: acquire or join live lock
  NestedLiveLockController->>BunTestChild: spawn healthy and refusal cases
  BunTestChild->>TestRunLock: validate lock capability
  BunTestChild-->>NestedLiveLockController: return receipt or refusal
  NestedLiveLockController->>TestRunLock: test foreign-owner timeout and release own lock
  NestedLiveLockController-->>WindowsRunner: emit JSON receipt and exit status
Loading

Merge Risk: ⚪ Minimal · up to 499ca

The Windows regression now runs through an isolated lock controller while preserving the outer batch no-queue setting. No actionable merge risk was identified.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #4991 requires coverage on the Windows batch lane while preserving outer OCX_TEST_NO_QUEUE=1. The PR summary states that the Windows test removes only the skip condition and that the controlle…
Out of Scope Changes check ✅ Passed The changed files are tests/ci-workflows/test-runner.test.ts and the new tests/helpers/nested-test-run-lock-controller.ts. The test rewrite and controller directly restore the Windows nested live-…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: the Windows nested live-lock test now uses its own lock owner through a standalone controller.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a979029fe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const controller = spawnSync(
process.execPath,
[helperPath("nested-test-run-lock-controller.ts"), root],
{ cwd: root, env: controllerEnv, encoding: "utf8", timeout: SPAWN_BUDGET_MS },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Increase the controller timeout to cover sequential children

On a loaded Windows runner, this 45-second timeout can terminate a healthy controller before it emits its receipt. The controller performs four sequential spawnSync calls, each explicitly allowed 15 seconds, may spend up to 10 seconds acquiring its lock, and then runs the foreign-owner probe; therefore valid individual operations can exceed the enclosing budget in aggregate. Size the controller/test timeout for the complete sequence or share one deadline across the child runs so that ordinary Windows startup contention does not make this regression test fail spuriously.

Useful? React with 👍 / 👎.

Adversarial review of the first commit found three real weaknesses. The
controller could spend more than the caller's 45s hard kill across four child
spawns, so a failure path could terminate it inside a spawn with the lock
still held and its teardown never reached. It is now handed an absolute
deadline 10s short of that kill and bounds every child by what is left of it,
minus a cleanup reserve.

A join failure inside registerMember can carry a member filename, and that
path runs before the controller learns its own token, so a redactor keyed on
that token was blind exactly where a leak was possible. Diagnostics now strip
every UUID-shaped substring, and receiptRedacted scans for one instead of
being vacuously true on the green path.

Two other receipts were weak: the acquire-timeout probe accepted the message
without waiting, and release checked only that our own owner file was gone.
They now require the elapsed floor and the planted foreign owner intact after
release. childrenReaped requires the full spawn count so a skipped scenario
cannot pass. lockOwned is renamed lockHeld because the controller joins an
existing owner when a wrapped run already published one.
…ee it

Moving the per-child timeout into the controller removed this file's only
spawn-options INTERNAL_DEADLINE_MS, so the cold-spawn warm-up guard stopped
matching it and its disposition became an orphan. That failed test 3/4 on
Linux and windows 7/9.

The deadline that bounds four cold Bun starts belongs to the case that owns
them, not to the helper, so the test now declares the child spawn options and
hands them over; the controller only narrows them to what its own deadline
still allows. The guard's inventory and its scan agree again, and the helper
no longer re-derives a budget constant.
@lidge-jun
lidge-jun merged commit 0429c0a into dev Sep 19, 2026
94 of 100 checks passed
@lidge-jun
lidge-jun deleted the codex/4991-nested-live-lock-owner branch September 19, 2026 04:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore Maintenance, CI, tests, refactors, or build changes (not a user-facing bug or feature).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant