Commit Graph
10 Commits
Author SHA1 Message Date
SoloDrex52andClaude Opus 4.8 f949947003 fix: make the test suite pass on Windows (#4260)
Refuse a read-only destination in _atomic_replace on Windows (nt-only guard;
os.replace there raises PermissionError and the fallback would silently clobber
it), plus test-only guards: utf-8 encoding kwargs, posix-shape path assertions,
FIFO/AF_UNIX/symlink skips, and a replace-retry for AV/indexer locks. No POSIX
behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 19:18:06 +01:00
ayushcodes10 0fd1f141d4 Guard os_replace_with_fallback against src equal to dst
Replacing a path with itself, if the initial os.replace call ever
fails for that call, crashed: the fallback backs dst up by renaming
it aside before landing the new content, and when src and dst are
the same path that rename moves src out from under itself, so the
final unlink of src then raised FileNotFoundError. Not reachable
through any current caller, every one passes a distinct temp file,
but cheap to guard against directly. Found by the graphify review
bot on PR 3509.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 5b108e1a1f)
2026-09-12 17:36:09 +01:00
ayushcodes10 4c27b61058 Restore the destination if the fallback swap fails
The fallback removed whatever sat at the destination and then
renamed the new content into place as two separate steps, so a
failure in the second step left nothing at that path at all, worse
than the original write it was trying to protect. Now renames the
existing destination aside as a backup instead of unlinking it, and
renames the backup straight back if the final rename fails, so a
mid swap failure restores the original rather than losing it. Found
by the graphify review bot on PR 3509.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit b56c77a107)
2026-09-12 17:36:09 +01:00
ayushcodes10 4489552b55 Preserve replace semantics for a symlinked destination
os.replace atomically swaps whatever sits at the destination, a
symlink included, by replacing the link itself. install.py relies on
exactly that for its managed skill symlinks, per #3286. The
fallback's plain shutil.copy2 does the opposite when the destination
is a symlink: opening it for writing follows the link and overwrites
whatever it points at instead of the link. Copies to a fresh temp
file beside the destination first, then swaps the destination out
from under it with an unlink plus rename, matching replace's own
behavior and, as a side effect, shrinking the window where a reader
could see a half written file down to two fast metadata operations
rather than however long the copy itself takes. Found by the graphify
review bot on PR 3509.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 39effa0c28)
2026-09-12 17:36:09 +01:00
ayushcodes10 df5a9042e3 Add a shared Windows replace fallback helper
os.replace can fail on Windows even when the temp file and the
destination are in the same directory on the same drive: WinError
17, "cannot move to a different disk drive", maps to a plain OSError
in Python rather than PermissionError, so the existing copy then
delete fallback for WinError 5 and 32 never caught it. Adds
os_replace_with_fallback, checking both PermissionError and winerror
17, and points the shared atomic writer at it instead of its own
inline PermissionError only version. Toward #3508.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 32cbf1f83e)
2026-09-12 17:36:09 +01:00
himanshupatro-334 7f87c3b08a fix(paths): bound atomic temp filename length
(cherry picked from commit 9d17b94aaf)
2026-09-06 16:17:36 +01:00
rajashidattapy cdf1f65656 test: probe-and-skip symlink tests where symlink creation is unavailable (#2642) 2026-08-12 20:56:28 +01:00
rajashidattapy 0df2a701a5 fix(paths): clear read-only bit before unlinking the atomic-write temp on Windows (#2622) 2026-08-12 20:56:28 +01:00
safishamsiandClaude Opus 4.8 16fe8f3020 fix(io): route the remaining graph/manifest writers through the atomic helper (follow-up to #1952)
The atomic-write PR left several writers on the old truncate-then-write path.
Route them through write_json_atomic so a crash mid-write can't corrupt them:
- the --no-cluster raw graph.json dump (a core graph.json writer)
- merge-graphs / merge-chunks / merge-semantic output
- .graphify_analysis.json and .graphify_labels.json sidecars
- global_graph.py's global-graph.json and global-manifest.json

write_json_atomic gains an ensure_ascii flag so the raw-UTF-8 writers
(labels, merge outputs) keep byte-for-byte output. Adds tests for the
Windows PermissionError copy fallback and ensure_ascii=False.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 23:48:07 +01:00
tpateeq f38e98012d fix(io): write graph.json and manifest.json atomically
graph.json (the clustered `to_json` write and the `--no-cluster`/merge raw
dumps) and manifest.json were written with a direct `open()`/`write_text`, so a
crash, kill, or disk-full mid-write left a truncated, unparseable file that the
next load or `detect_incremental` then failed on.

Add `write_text_atomic`/`write_json_atomic` in graphify.paths (temp file in the
same directory + `os.replace`; JSON is streamed into the temp, not materialized
as one string) and route the graph.json writers (export.to_json,
cli._prune_graph_json_sources, the merge driver) plus detect.save_manifest
through them. The helper preserves the destination's mode (an atomic replace
never tightens 0644 to mkstemp's 0600), writes through a symlinked destination
(shared-output setups), and falls back to copy-then-delete on a Windows
os.replace lock — matching graphify.cache's existing atomic writer. On failure
the previous file is left intact and the temp removed. Not a power-loss
durability guarantee (no fsync, consistent with the rest of the codebase).
2026-07-16 23:42:47 +01:00