Repository navigation
test(external-core-helpers): detect helper restarts with a per-test tripwire, not host-global /tmp logs - #5181
Conversation
…ripwire, not host-global /tmp logs test_valid_launch_and_restart_do_not_manage_owned_helpers snapshotted /tmp/core-input-watch.log and /tmp/core-heartbeat.log, which any other launcher-running suite in the same CI job also writes. It now swaps the fixture repo's copies of core-input-watch.py and core_heartbeat.py for a tripwire during the launches, so only a start by this test's launcher can fail it. Adds a regression that simulates a concurrent writer of the global logs. Closes #5180 Stand: Echo Act IV Pro Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 91%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changesNo lines with coverage information in this diff. |
john-the-dev
left a comment
There was a problem hiding this comment.
Approve at be76d36. Test-only: tests/external-core-helpers.test.py, +40/-18.
-
The tripwire covers every launcher start path.
start-cli.shstarts the helpers only as$REPO/src/core-input-watch.pyand$REPO/src/core_heartbeat.py(:308, :346, plus the heartbeat--stopat :437), and$REPOis the fixture's temp repo. I mutated the launcher and checked each case:- external mode also starting
core_heartbeat.py: fails, as it should; - external mode starting
core-input-watch.pywith barepython3and no/tmplog: fails at this head, while main's version passes, so the new check is stricter; - restart running heartbeat
--stop: fails.
The launcher was restored byte-identical (sha256), with no
__pycache__left. - external mode also starting
-
Regression: head's new test, transplanted into main's file, fails with the mtime/sha mismatch, and passes here. At this head the file is 23/23, three runs out of three, and the real
/tmp/core-*.logsize and mtime are unchanged before and after. -
review-checks: PASS. 29 checks pass and 1 is skipped.
-
Codex-core verification (Rui's Codex agent, at this head): 23/23 on a macOS Codex host. Injecting an extra helper start into either ensure function made the test fail, for both helpers, and the concurrent-log regression fails on the parent
f7b69fc81.
Non-blocking:
- The fixed
time.sleep(0.3)after each launch means that under heavy load a regressed start could land after the check, a false pass and never a flake. Polling for the marker would close it. - Removing the early return in
ensure_core_heartbeatis missed by both main and this head. Thepgrepat :339 finds the already-running external helper, so nothing new starts. That gap predates this PR. shutdown-sentinel-immediate-exit-controls.test.pystill writes the host-global logs, as the PR body notes. That's a separate hygiene follow-up.
keweichen
left a comment
There was a problem hiding this comment.
APPROVE at be76d36f8a0084a0fee4f72b85c611d26eca1967. The change is ready on technical merit with no blocking finding; the PR as a whole is not merge-ready until the requested qingyun-wu code-owner approval is recorded.
Independent evidence:
- The bug exists on the exact parent/current-main SHA
f7b69fc81ce8f724ade258dd997457809d98aa86: two unrelated CI jobs ran this unchanged test and failed at the host-global/tmpsnapshot while the neighboring real launcher test wrote those logs. Transplanting only the new concurrent-writer regression onto that parent collected the added test and failed with the expected mtime/hash mismatch. - At this head, all 23 named tests pass in three consecutive full-file runs; the new regression passes in isolation. A mutation that starts the fixture heartbeat in external-helper mode, without depending on either global log, fails on the new marker with
core_heartbeat.py, so the replacement instrument has a positive control. - The diff is one test file (+40/-18),
git diff --checkandscripts/review-checks.shpass, all current CI is successful or intentionally skipped (28 success, 1 Windows skip), andlicense/clais successful. Worst-case user disruption is limited to test signal: no production code or runtime behavior changes, and fixture script edits are temp-repo scoped and restored infinally.
Non-blocking follow-up: the fixed time.sleep(0.3) leaves a false-negative window for an unusually delayed background child; a controlled delayed-marker mutation demonstrated that limitation. Polling/synchronizing the marker, and eventually tripwiring the helper-death replacement path too, would make the future-regression claim stronger. This does not reintroduce the current cross-shard false positive or change the present launcher behavior.
|
@qingyun-wu — this PR is approved by john-the-dev and keweichen (2 non-codeowner approvals) at head be76d36, but |
qingyun-wu
left a comment
There was a problem hiding this comment.
Code-owner approval by Qingyun via the [PA] Decisions page (Decision: approve) at be76d36. Posted by Qingyun's Sutando.
|
@cla-assistant check |
Closes #5180
Cause
test_valid_launch_and_restart_do_not_manage_owned_helpersasserted that host-global/tmp/core-input-watch.logand/tmp/core-heartbeat.log(mtime_ns + sha256) were unchanged across a launch and restart. Since the reshard landed, python standalone shard 2/7 schedulestests/external-core-helpers.test.py(file 5) right next totests/shutdown-sentinel-immediate-exit-controls.test.py(file 6), so they start together in parallel lanes. That suite runs the realsrc/agent/codex/cli/start-cli.sh(codex-detached,codex-ttybranches) without--external-helpers, soensure_core_monitor/ensure_core_heartbeatredirect into exactly those two/tmpfiles. Same file list and same failure in both runs:(core-heartbeat.log absent before, present after; core-input-watch.log truncated to empty.)
Fix (test only, no production change)
During the two launches the test swaps the fixture repo's copies of
core-input-watch.pyandcore_heartbeat.pyfor a tripwire that writes a marker in the test's own temp dir. The externally owned helpers already running keep their loaded code, and the receipt validator checks paths, not contents, so validation is unaffected. Any start of either helper by this test's launcher, through any interpreter, now fails the test; writes by other suites to/tmpno longer can. The originals are restored infinally.New regression
test_concurrent_writer_of_host_global_helper_logs_does_not_fail_launchpatchesPath.stat/Path.read_bytesfor the two global log paths only, so every read sees a new write (a deterministic concurrent writer), and runs the launch/restart test under it. It never touches the real/tmpfiles.Not changed here:
shutdown-sentinel-immediate-exit-controls.test.pystill writes the host-global logs through the real launcher; that is a separate hygiene issue.Before / after
Regression method applied to the origin/main (f7b69fc) version of the test file:
HEAD:
Full file at HEAD, 5 consecutive runs:
Ran 23 tests ... OKx5. The real/tmp/core-*.logfiles had the same size and mtime before and after.Mutation control (the test still catches the defect it exists for): making the codex launcher also start
core_heartbeat.pyin external-helpers mode fails it:git diff origin/main | bash scripts/review-checks.sh->review-checks: PASS (hardcoded-paths + root-artifacts + prose-cap clean)🤖 Generated with Claude Code