Skip to content

fix(test): keep the permission-contract receipt alive through fixture-cleanup failures - #881

Merged
danshapiro merged 1 commit into
mainfrom
fix/claude-perm-fixture-cleanup-race
Oct 30, 2026
Merged

danshapiro merged 1 commit into
mainfrom
fix/claude-perm-fixture-cleanup-race

Conversation

@danshapiro

Copy link
Copy Markdown
Owner

Summary

A cloud vitest shard lost a whole base-gate run (2026-10-29) to an unhandled
ENOTEMPTY from fixture.cleanup() in the Claude permission-contract harness:
the sidecar's native CLI outlived the sidecar-only SIGKILL and kept writing
into the fixture while fs.rm swept it, the rejection escaped
runScenario's finally, and the harness died before printing its one-line
receipt — so the suite failed with "expected one receipt line" and the shard
crashed out.

Three layers, test-file-only (no product code):

  1. runScenario now spawns the sidecar detached and, on the force path,
    kills its whole process group (the native CLI is the fixture's concurrent
    writer), then awaits the exit before cleanup.
  2. fixture.cleanup() retries ENOTEMPTY/EBUSY stragglers
    (maxRetries: 10, retryDelay: 100) — fs.rm's default maxRetries=0
    turns any file recreated between the unlink sweep and the final rmdir
    into a rejection.
  3. A cleanup failure is caught and logged (fixture.cleanup_failed); the
    receipt reports it as fixtureRemoved=false instead of the process
    crashing and eating the receipt for every scenario at once.

Test evidence

  • New sabotage test (red first — the harness crashed with zero stdout on
    the unwritable-fixture EACCES; then green): with every fixture root made
    unwritable after its setup, the harness still prints one parseable receipt
    with fixtureRemoved=false for all eight scenarios and exits 1.
  • Impacted sweep: all 7 suites in test/integration/providers/ pass
    (110 tests), including the real-CLI contract and sidecar-shutdown pins.
  • pnpm run typecheck clean. No Rust touched.

References

  • Base-gate failure attribution: Cloud Run job freshell-vitest-31cb9ad69f5b-9qozeu
    (2026-10-29T23:05Z), shard stderr shows the unhandled
    ENOTEMPTY rmdir /tmp/freshell-claude-perm-shapes-* crash.

…-cleanup failures

A cloud vitest shard lost a whole base-gate run (2026-10-29) to an unhandled
ENOTEMPTY from fixture.cleanup(): the sidecar's native CLI outlived the
sidecar-only SIGKILL and kept writing into the fixture while fs.rm swept it,
the rejection escaped runScenario's finally, and the harness died before
printing its one receipt line.

Three layers:
- runScenario kills the sidecar's whole process group (detached spawn) and
  awaits the exit before cleanup, so the CLI cannot be alive under the rm.
- fixture.cleanup() retries ENOTEMPTY/EBUSY stragglers (maxRetries=10).
- A cleanup failure is caught and logged; the receipt reports it as
  fixtureRemoved=false instead of crashing the process.

The new sabotage test pins the containment: with every fixture root made
unwritable after its setup, the harness still prints one parseable receipt
with fixtureRemoved=false for all eight scenarios.
@danshapiro
danshapiro merged commit 5be2481 into main Oct 30, 2026
3 checks passed
@danshapiro
danshapiro deleted the fix/claude-perm-fixture-cleanup-race branch October 30, 2026 01:39
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