You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
_rebuild_lock unlinks .rebuild.lock on release, so two rebuilds can hold the lock at once (POSIX) #4273
graphify/watch.py_rebuild_lock opens graphify-out/.rebuild.lock (L200), takes fcntl.flock (L209), and on release calls LOCK_UN (L244) and then unlinks the path (L254-258).
flock locks the open file (the inode), not the path. Once the path is unlinked while another process is already waiting on the old inode, a later caller creates a new inode and locks it immediately:
A holds the lock.
B, a blocking waiter (graphify update → _rebuild_code(..., block_on_lock=True), cli.py:2563), opens the path → inode X and waits in flock.
A finishes: LOCK_UN, then unlink(path). B now holds the lock on X, which no longer has a path.
C (another graphify update, a post-commit/post-checkout hook, or watch) opens the path → new inode Y → flock succeeds at once.
B and C are now both inside _rebuild_code and write the same outputs (.graph.tmp.json, graph.json, the report). This only needs one blocking waiter plus any later caller, so two terminals/agents running graphify update on the same repo are enough.
0.00s A enter
1.00s A exit
1.00s B enter
1.38s C enter <- C enters while B is still holding the lock
1.88s C exit
2.50s B exit
Expected
At most one holder of the rebuild lock at a time.
Possible fixes
Keep the lock file on release (rewrite/truncate the PID line instead of unlinking). Tools that currently poll for the file's absence could try a non-blocking flock, or read an explicit "done" marker.
Or keep unlink-on-release but make acquisition robust: after flock succeeds, compare os.fstat(fh.fileno()).st_ino with os.stat(lock_path).st_ino and retry when they differ (or when the path is gone).
Related: #858 (unlink was added there for the stale-PID problem), #1037, #1059, #3881.
Workaround we use for now: serialize every call through a lock file that is never removed, e.g. /usr/bin/lockf -k .git/graphify-update.lock graphify update . on macOS (flock from util-linux on Linux).
Activity
github-actions commentedon Oct 10, 2026
Thanks for opening this issue, @Ethan-JY-Park. A maintainer will take a look soon.
If you would like to discuss it in real time, come say hi on our Discord server. For longer-form questions and ideas there is also GitHub Discussions.
To help us triage, please make sure the report includes what you expected, what actually happened, and the steps (and a small sample) to reproduce it.
fix(watch): close the rebuild lock unlink-vs-flock race (#4273)
safishamsi commentedon Oct 10, 2026
Fixed in 0.9.85 (now on PyPI) via #4277. Thanks @ayushcodes10!