Skip to content

test: settle Windows ACL work and owner locks before fixture removal - #7528

Open
lidge-jun wants to merge 2 commits into
devfrom
test/windows-teardown-handles
Open

lidge-jun wants to merge 2 commits into
devfrom
test/windows-teardown-handles

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

#7515 reported hosted-Windows fixture teardowns running out of their 15 s removal budget with EPERM or EBUSY after every assertion had passed. Its EBUSY case in main-account-hard-lock-fallback-routing.test.ts had its own cause on #7482's branch (routing-history.sqlite held open by the hop classifier) and was fixed there, merged to dev as 1025acd4b1 (2.91.0). This PR covers the remaining EPERM cases (server-management-auth.test.ts, codex-auth-context.test.ts) and makes the shared teardown refuse homes that a native-main owner still holds. No retry count, sleep, or timeout changes.

What was blocking deletion. In the two EPERM cases, the lock diagnostic shows the same blocker. The native-main lock file under the test home (.opencodex-native-main.owner.sqlite in server-management-auth, .claim.sqlite in codex-auth-context) had an empty DACL. Every other file kept normal entries. I reproduced that signature on a real Windows host: a file with zero ACEs still renames through the parent's delete-child right, which is why the diagnostic printed rename:ok, but recursive rm fails. The only thing that writes that file's ACL is real async icacls, running in suites that assert nothing about ACLs.

  • tests/codex-integration/codex-auth-context.test.ts: the suite already says real icacls doesn't belong in it, but it stubbed only the sync runner. The claim lock is hardened through the async runner. It now stubs both and tears down through drainAndRemoveFixtureRoots. The old teardown used the blocking remover directly, which never waited for ACL children and blocked the event loop.
  • tests/server/server-management-auth.test.ts: stubs the async runner for the suite (sync-runner cases still inject their own). The stub stays installed until removal finishes.
  • tests/codex-integration/main-account-hard-lock-fallback-routing.test.ts: separate from the EBUSY fixed by feat(fallback): OCX-11 fallback off per scope, with home_unreachable on Children #7482. An instrumented Windows run on dev found 2 of 29 cases reaching removal while the config-dir ACL harden (config-dir-acl-harden, a live icacls child under the home) was still in flight. Teardown now uses the shared drain helper. This does not touch feat(fallback): OCX-11 fallback off per scope, with home_unreachable on Children #7482's code.
  • tests/helpers/fixture-teardown.ts + src/codex/native-main-owner.ts: before removing a root, the shared teardown asks the new nativeMainOwnerHomesOpenAtOrBelow(root) whether a native-main owner below it is still open. That includes an owner still acquiring (descriptor and SQLite handle held in locals while its hardener runs), one waiting on a retry timer, and one that is closing. If so, it keeps that root and names the owner immediately, without spending the retry budget on an EBUSY that names nothing. structure/ops/test-sandbox-cleanup.md documents the check.

This does not cover the third pattern in the issue thread. #7501 (e501371) fixed timing races; it changed none of these teardowns.

Refs #7515

Verification

  • Windows host, Bun 1.4.3 (the CI testRunnerBun), isolated HOME/USERPROFILE/LOCALAPPDATA/TEMP/OPENCODEX_HOME/CODEX_HOME/CLAUDE_CONFIG_DIR: bun test on the four changed suites → 171 pass / 0 fail. A temporary probe at the removal step (not committed) logged acl-pending=false bg=[] for all 174 removals.
  • Windows, same isolation, dev before the fix: the probe on the hard-lock fallback file showed acl-pending=true bg=["config-dir-acl-harden"] for 2 of 29 cases.
  • Windows experiments: an open SQLite connection gives EBUSY on both rm and rename; a zero-ACE file gives rename:ok while rm fails; icacls /T lists a zero-ACE file as a bare path, which matches the CI logs.
  • macOS: focused set (helper suite, both changed suites, and every other drainAndRemoveFixtureRoots user plus native-main-owner-lifetime) → 282 pass / 0 fail. Hard-lock fallback file plus helper suite → 43 pass.
  • bun run test:changed with Bun 1.4.3 (ran 1542 files) → 34521 pass / 42 skip / 5 fail. All 5 are Antigravity UA/discovery cases that fail only because GOOGLE_ANTIGRAVITY_USER_AGENT is set in my shell; unset, those files pass 22/22.
  • tsc --noEmit exit 0, bun run structure:check, bun scripts/file-size-ratchet.ts, bun run privacy:scan, and git diff --check all pass.
  • Review follow-up (e542512): a delayed-hardener regression fails against the old probe (0/1) and passes with the fix. The 13 teardown-related files pass 312/312 on macOS. Not re-run on Windows for this commit.
  • ci.yml lane=all at 0577267: run 39790983126. All 9 Windows shards succeeded, but the run concluded failure because the ci aggregator hit GitHub API faults (null conclusions, HTTP 502, a partial job list). At e542512: https://github.com/lidge-jun/opencodex/actions/runs/39804032261 (pending).

Scope: this does not claim #7515 as a whole is resolved. The empty-DACL interleaving remains unexplained, and the EPERM fixes are verified only as far as the runs below. Not proven: I could not reproduce the empty-DACL interleaving itself off CI. Real owner, claim, and dir-harden flows under concurrency on Windows produced 0 of 15; directory propagation, killed icacls, and Bun mode handling were each ruled out. The fix removes the only writer of that ACL from these suites; it does not explain how production reached that state.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed. (Test-only behavior plus one diagnostic export; no user-facing change.)
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. (No product ACL code changed; the stubs are test-scoped and restored after removal.)

Summary by CodeRabbit

  • Bug Fixes
    • Temporary test environments are no longer removed while native ownership is active, including during acquisition, release, or a pending retry. Cleanup reports the affected location and can still remove other ready locations.
  • Tests
    • Updated cleanup checks to cover active and released ownership, pending work, and permission-command handling.
  • Documentation
    • Clarified when temporary locations are retained and how cleanup failures affect individual locations.

Three Windows fixture teardowns exhausted the removal retry budget (#7515).
Their lock diagnostics show what blocked removal: in two of them the
native-main owner or claim lock file under the home had an empty DACL.
Real async icacls had rewritten it in suites that assert nothing about
ACLs. Rename still worked, but recursive rm got EPERM.

- codex-auth-context: the suite meant to keep real icacls out, but it
  stubbed only the sync runner, and the claim lock is hardened through the
  async one. Stub both, and tear down with drainAndRemoveFixtureRoots in
  place of the blocking remover, which never waited for ACL children.
- server-management-auth: stub the async runner for the suite; the stub
  stays installed until removal ends.
- main-account-hard-lock-fallback-routing: two cases reached removal with
  the config-dir ACL harden still in flight. Use the shared drain teardown.
- fixture-teardown: before removing a root, refuse it and name any
  native-main owner still holding its SQLite lock below it, via the new
  nativeMainOwnerHomesOpenAtOrBelow probe. The teardown now fails at
  once, without spending the 15 s retry budget on EBUSY.

No retry count, sleep, or timeout changes.

Refs #7515
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner October 22, 2026 13:54
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 0577267 and e542512.

📒 Files selected for processing: 3
  • src/codex/native-main-owner.ts
  • structure/ops/test-sandbox-cleanup.md
  • tests/ci-workflows/fixture-teardown-helper.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

📝 Walkthrough

Fixture teardown now checks for open native-main owners after ACL work drains and before it removes a root. Tests use the shared asynchronous teardown helper and stub asynchronous ACL commands where needed.

Changes: Fixture teardown

Layer / File(s) Summary
Detect open owners and guard root removal
src/codex/native-main-owner.ts, tests/helpers/fixture-teardown.ts
The native-main owner lookup returns open owner homes at or below a root. Teardown checks for open owners after ACL draining and marks affected roots unready for removal.
Validate owner-aware root removal
tests/ci-workflows/fixture-teardown-helper.test.ts, structure/ops/test-sandbox-cleanup.md
Tests cover injected open owners, path-scoped owner lookup, and owners paused during acquisition or release. The cleanup description documents the owner check and root-specific removal behavior.
Update integration test teardown
tests/codex-integration/*, tests/server/server-management-auth.test.ts
Integration tests use shared asynchronous fixture teardown. ACL-related test setup stubs the asynchronous icacls runner and resets it during teardown.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

  • lidge-jun/opencodex#7515 reports Windows fixture-teardown failures with EBUSY and EPERM. This PR changes the same teardown paths, but the issue remains open and resolution is not established.

Suggested reviewers: luvs01

Merge Risk: ⚪ Minimal · up to e5425

The affected fixtures now wait for ACL work and preserve roots with open owners. No actionable merge-blocking risk remains after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 6 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: waiting for Windows ACL work and native owner locks before fixture removal.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 6 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ 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
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

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

@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: 05772674fd

ℹ️ 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".

Comment thread src/codex/native-main-owner.ts Outdated
Comment thread tests/helpers/fixture-teardown.ts
@chatgpt-codex-connector

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-10-22T13:57:48.081541Z 0577267 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.

@Ingwannu Ingwannu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed all six changed files at 05772674fd0c277b39969377ad15366abf1ff7cd. Waiting for fixture-owned ACL work and retaining per-root failure handling are improvements, but the new owner probe has a reproducible preparation gap.

src/codex/native-main-owner.ts:315: prepareOwnerDatabase opens a StableLockFile descriptor and SQLite database, then awaits hardenPath. Until that await completes, both handles are local variables and neither entry.file nor entry.database is published. nativeMainOwnerHomesOpenAtOrBelow skips that entry, so assertNoOpenLocks can permit removal even though the process still owns handles.

Independent Linux probe on this exact head, using only an owned temporary home and the existing injected hardenPath seam: pause the hardener, count actual /proc/self/fd targets beneath that home, then query the new export. Result: 2 open owned descriptors, 0 detected owners. The probe resumes the hardener and awaits owner release before cleanup; it never tries to delete an open Windows database. Please track preparation/in-flight handles, or conservatively treat that acquiring state as open, including detached entries being released. Add a delayed-hardener regression that verifies teardown preserves the root and that it becomes removable only after the preparation/release finishes. The existing new test waits for held, so it cannot detect this gap.

Also update the owning structure/ops/test-sandbox-cleanup.md paragraph: it currently says ACL drain proceeds to root removal and omits the new owner-lock check, per-root preservation and immediate owner diagnostic. This is a contract change even though production ACL behavior is unchanged.

Independent bounded Bun 1.4.3 / Linux batch of fixture-teardown-helper, codex-auth-context, main-account-hard-lock-fallback-routing and server-management-auth: 171 passed / 0 failed, 821 assertions. That passing batch does not cover the demonstrated preparation state. The manually dispatched native lane remains in progress and ordinary Cross-platform CI is cancelled at this snapshot; neither is reported as passed. No retry/timeout increase, production config, service or real native client change was performed here. #7515's unproven empty-DACL interleaving and third pattern must not be declared resolved by these receipts.

The teardown probe saw only published handles. While prepareOwnerDatabase
awaits its hardener, the stable descriptor and SQLite database live in
locals, so the probe reported no owner and teardown could remove a home
the process still held. Treat an in-flight acquisition, a pending retry
timer and any detached (closing) entry as open.

Regression: a real owner with its hardener paused keeps its root through
acquisition and release, and the root is removed only once release ends.
Document the owner check in structure/ops/test-sandbox-cleanup.md.

Refs #7515
@lidge-jun

Copy link
Copy Markdown
Owner Author

Thanks, the preparation gap is real. Fixed in e542512.

  • nativeMainOwnerHomesOpenAtOrBelow now treats an owner as open when it has an acquisition in flight (drive, which covers the descriptor and SQLite database held in locals while hardenPath runs), a pending retry timer that would reopen them, or any detached entry that is still closing. A published database or side descriptor still counts as before.
  • New regression in tests/ci-workflows/fixture-teardown-helper.test.ts ("an owner paused mid-acquisition…"): a real owner with its hardener paused. While it is acquiring, teardown keeps the root and names the owner. It keeps the root again while release() waits on the paused acquisition. Only after the hardener resumes and release finishes does the probe return empty and teardown remove the root. Against the previous probe this test fails (0 pass / 1 fail); with the fix it passes.
  • structure/ops/test-sandbox-cleanup.md now describes the owner-lock check, per-root preservation, and the immediate owner diagnostic.

Local (macOS, isolated homes): the 13 files that use or test this teardown pass 312/312; tsc --noEmit exits 0; structure:check and privacy:scan pass. Not re-run on Windows for this commit. lane=all is dispatched on e542512: https://github.com/lidge-jun/opencodex/actions/runs/39804032261 (pending, not claimed).

Agreed on scope: this PR does not claim the empty-DACL interleaving or #7515 as a whole is resolved. It removes the ACL writer from these fixtures and makes teardown refuse open owners. The PR body now says that explicitly.

@lidge-jun

Copy link
Copy Markdown
Owner Author

@Ingwannu Response to your CHANGES_REQUESTED review on 0577267, point by point. Current head: e542512.

  1. Owner probe misses the preparation window (src/codex/native-main-owner.ts). Fixed in e542512. nativeMainOwnerHomesOpenAtOrBelow now treats an entry as open when an acquisition is in flight (entry.drive, which covers the descriptor and SQLite handle held in locals during hardenPath), when a retry timer is pending, or when a detached entry is still closing (lines 312-318).
  2. Delayed-hardener regression (tests/ci-workflows/fixture-teardown-helper.test.ts:167). The test pauses a real owner's hardener. While the owner is acquiring, and again while release() waits on that acquisition, teardown keeps the root and names the owner. The probe goes empty and the root is removed only after the hardener resumes and the release finishes. Red→green: 0/1 against the previous probe, passing with the fix.
  3. Owned structure doc (structure/ops/test-sandbox-cleanup.md:75-84). Now documents the owner-lock check after the ACL drain, per-root preservation, and the immediate owner diagnostic. bun run structure:check passes.
  4. Do not declare [Bug][CI]: Windows fixture teardown exhausts the remove retry budget (EBUSY/EPERM) in two existing tests #7515 resolved. The PR body now says so explicitly. It also records that the EBUSY case in the hard-lock fallback test was fixed by feat(fallback): OCX-11 fallback off per scope, with home_unreachable on Children #7482 (1025acd4b1); this PR's change to that file only closes a separate gap, an in-flight config-dir ACL drain at removal. The empty-DACL interleaving remains unexplained.

Verification at e542512:

  • macOS, isolated homes: the 13 files that use or test the teardown pass 312/312; tsc --noEmit exits 0; structure:check and privacy:scan pass. This commit was not re-run on a Windows host.
  • lane=all run 39804032261: all 30 test jobs succeeded, including Windows 9/9. The ci aggregator failed twice reading the shard results from the jobs API (the latest attempt found 7 of 9 legs, all of which are recorded as success), so the run conclusion is still failure. I am not reporting that run as passed.

Requesting your re-review.

@lidge-jun
lidge-jun requested a review from Ingwannu October 22, 2026 17:48

This branch has not been deployed

No deployments
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.

2 participants