Repository navigation
test(watch): script only the watch loop's sleep in drive_watch - #4620
madhusudanpatnaik wants to merge 1 commit into
Conversation
drive_watch replaced `sleep` on the shared `time` module with its event
pump, so every sleeper in the process ran the script, not just the watch
loop. A rebuild runs `git rev-parse HEAD` through subprocess.run(timeout=3),
whose wait loop sleeps when git has closed its output but has not been
reaped yet. That sleep then consumed the next scripted event, or raised the
end-of-script KeyboardInterrupt inside the rebuild; _git_head only catches
Exception, so the watcher stopped before extract() and the last change never
reached graph.json. In CI this surfaced as
test_watch_root_config_lifecycle_rebuilds_and_refreshes_event_mapping[modify]
failing with `'changed()' not in {'initial()', ...}`.
Patch watch.py's own `time` reference instead: sleep is scripted, every other
attribute resolves to the real module, and sleeps elsewhere stay real.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
324cb2e to
1dce423
Compare
There was a problem hiding this comment.
Graphify reviewed this change.
No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.
Not checked: tests were not run; no formal proof of the changed code.
No blocking issues. I didn't find anything I'd hold the merge for.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 0 functions depend on the 0 functions this change touches.
Health — grade A; no new coupling hotspots.
Verification — 0 functions in the blast radius were not formally verified this run (proofs are advisory here).
Diff could not be reliably attributed to this PR: the base/head merge-base was unreachable in the shallow clone, so detailed findings were skipped (rather than emit a two-dot diff, which would misattribute base-branch changes).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Test selection
Test selection
0 of 436 test file(s) selected (0%) via static blast radius.
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

What does this PR do?
Fixes an intermittent CI failure in the watch tests. Test-only: nothing under
graphify/changes.drive_watch(intests/test_language_override_lifecycle.py, also used bytests/test_pr10_watch_overrides.py) scripts the watch loop by replacingtime.sleepwith an event pump. Each call dispatches the next scripted event, and once the script runs out it raisesKeyboardInterruptto stopwatch(). It installed the pump withmonkeypatch.setattr(watch_mod.time, "sleep", pump).watch_mod.timeis the sharedtimemodule, so every sleeper in the process ran the pump, not only the watch loop.A rebuild calls
_git_head(), which issubprocess.run(["git", "rev-parse", "HEAD"], ..., timeout=3). When git has closed its output but hasn't been reaped yet, CPython'sPopen._waitpolls withtime.sleep(). That sleep then consumed the next scripted event, or, after the last one, raisedKeyboardInterruptinside the rebuild._git_headcatches onlyException, so the interrupt propagated,watch()printed "Stopped." beforeextract()ran, and the last change never reachedgraph.json.It failed this way in CI on #4580 (run 39500320206, test (3.13); the PR doesn't touch watch code):
The fix replaces watch.py's own
timereference with a small object whosesleepis the pump and whose other attributes resolve to the real module. The watch loop is still fully scripted, because itssleepandmonotonicboth go throughwatch_mod.time.subprocessand everything else keep the realtime.sleep.Type of change
Verification & Invariants
Invariant: the script advances only when the watch loop itself sleeps.
Reproduced on v8 (0.9.93, b90b9df): I repeated the
[modify]scenario 3,000 times in one process. It failed 3 times, each with the same labels as in CI. I loggedPopen._try_waitaround each rebuild. In every failure, the last rebuild'sgit rev-parsepoll returned "not exited yet" just before the watcher stopped, which is the sleep path. In passing runs the poll returned "exited" on the first try.With this PR: 3,000 runs, 0 failures. The race still occurred in 2 of them (4 subprocess sleeps during the final rebuild), and those runs passed.
Regression test
test_drive_watch_scripts_only_the_watch_loop_sleep: a sleep happens inside_git_headduring the rebuild, where subprocess's wait loop sleeps. On v8 it fails on every run, because the watcher stops mid-rebuild andgraph.jsonis never written. It passes here.No other test patches
time.sleepthis way.drive_watchis the only place.Read the CONTRIBUTING.md guide.
Reproduced the issue and identified the invariant.
Made the smallest fix necessary.
Added a regression test (if bug fix) or isolated boundary test.
Kept the PR description synchronized with the final implementation.
Documented any limitations / unsupported cases explicitly.
Limitation:
_rebuild_lock's Windows retry (time.sleep(0.05), in watch.py) still goes through the pump, as it did before. It only runs on lock contention, which these tests don't create. I left it unchanged.How was this tested?
Graphify-specific checklist
uv run python -m tools.skillgen --bless) when changing their source fragments. (No source fragments touched;--checkpasses.)