test: pay macOS's first-run check before timing wrapper fixtures
The Claude wrapper regressions lane failed in 12 of 45 runs on the fleet Mac minis and 0 of 138 on Blacksmith, each time with a different message. The mutual-shim test in the CLI product lane timed out the same way. In both, the test's own fixtures lost a race with a macOS first-run check. Each fixture now pays that check before its timed run. No wrapper budget or test guard changes.
Cause
macOS 26 blocks the first run of every newly written executable, copies included, while syspolicyd assesses it: a Gatekeeper scan, a notarization lookup and an XProtect scan. That costs 0.15–0.4 s on an idle Mac and seconds on a loaded mini. A second run of the same file costs 5–20 ms.
tests/test_claude_wrapper_hooks.py writes a fresh fake claude and fake cmux for every case, and copies the wrapper to a fresh path, then runs them for the first time inside the wrapper's own budgets:
- The 1 s hook-settings generator. When the check eats that budget, the wrapper falls back to minimal hooks. That produces:
standard settings: Claude must receive the bundled CLI document byte for bytecmux hooks lostexpected one Node validation, ran 0
- The 0.75 s
claude --helpprobe. When the check eats that budget, the wrapper caches an empty catalog. That produces:fallback success: failure was not cached: []expected one help call: []- a
ProcessLookupErrorthat hid the rest of the results
- The 2 s and 5 s whole-process timeouts of the large-settings checks, which time the wrapper copy's first run.
tests/test_claude_wrapper_mutual_shim_loop.py runs fresh wrapper copies and shims inside each 5 s guard, so it hit the same check.
Real installs were run before, so they never pay this inside those budgets. #15768 fixed the Hermes wrapper tests the same way.
Change
- Every fixture now exits at once when
CMUX_TEST_PRIME_EXECis set, and is run once that way as soon as it is written. - Every wrapper copy, in both files, is primed with
PATH=/usr/bin:/binand no other environment. The wrapper then finds noclaudeand exits 127 before it writes anything. It has no priming guard of its own, and the runner'sPATHcould reach a realclaude. - The native fake
claudeis primed with--help. - The cancellation case records a failure instead of raising when the wrapper exits before the interrupt. It also waits until the help PID log holds both PIDs: the shell creates that file before
printfwrites to it.
Verification
- Under load, locally: 6 parallel workers ran the timing-sensitive checks. Unprimed, 5 of 6 workers failed with the CI messages; primed, 0 of 6.
- Claude wrapper, locally:
python3 tests/test_claude_wrapper_hooks.pypasses in full with theCMUX*variables of the cmux session it ran in removed (f21ecc11f27). With them set, only thecomputer usesocket-path checks fail, because they read this machine's own cmux runtime ID; CI has no such variables. - Claude wrapper, CI: this PR's
Claude wrapper regressionslane runs on the fleet minis. It passed on 7bfa3b3. On c9b43ec its first attempt ran the file in 153 s with 0 failures, but the job had already waited 13 minutes for a fleet slot, so the lane's 15-minute limit cancelled it. The rerun passed. - Mutual shim: this PR skips the
macosjob, which hosts the only lane that runstest_claude_wrapper_mutual_shim_loop.py. The validation PR #15960 includes this change, and itsCLI product testslane ran the file on cmux14:tests/test_claude_wrapper_mutual_shim_loop.py ok in 10.8s(job). It also passes locally.
Refs #15488.
Changelog
- none
🤖 Generated with Claude Code
Summary by CodeRabbit
- Tests
- Improved setup for timed wrapper and hook tests by priming test executables and copied wrappers before use.
- Added safeguards to prevent fixture executables from performing normal work during priming, and standardized the restricted environment used for setup.
- Updated cancellation-test handling for cases where the help process does not start or the wrapper exits before process-group signaling.