Repository navigation
fix(daemon): do not stop on a file Windows reports as access denied; fix the graph.json Windows flake - #212
Merged
Merged
Conversation
std::filesystem::exists(path) throws when the path cannot be stat'ed. Windows reports a file that an editor is replacing as "Access is denied", so the enrichment walk (classify_cached_file, is_valid_cached_record) and the watcher's incremental update threw, and the daemon stopped: exit 1 since #179, std::terminate before. Seen on windows-default CI as "enrichment refresh failed: ... exists: Access is denied ... docs\guide.md". Use the error_code overloads. A file that cannot be checked counts as absent for this pass, as the neighbouring file_size/last_write_time checks already treat it; the watcher reports it again once written. Regression tests reproduce the throw on POSIX with an unsearchable parent directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dows daemon_server_test polled graph.json and the stat index with std::ifstream every 20 ms. MSVC opens without FILE_SHARE_DELETE, so the daemon's MoveFileExW replace failed with access denied for its whole 10 s retry window and the test gave up (windows-release flake "incremental state re-persisted to graph.json"). Restart Manager named the test process as the only holder. read_shared opens with FILE_SHARE_DELETE on Windows, so the replace goes through while a read is in flight; POSIX is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PR brief: 0 agent commits · 12 of 110 test files reach this diffRun at least these. Selection is a safe superset; it never marks a test safe to skip.
What each commit did
Claims checkedno checkpoint on this branch makes a claim the graph can check What each changed file reaches
Paths above are under Tests to run (12)
Blast radius by changed file: 41 dependents
blastline 0.13.0. Selection is advisory unless your workflow gates on it. How selection works. Graph |
Review of #212: classify_cached_file returned Deleted when exists() failed, so a full stat rescan that met a file mid-save on Windows dropped that file's nodes until the next edit. Unreadable makes every caller keep the last verified entry (full_stat_index_rescan refuses, the incremental path and the chunk planner skip). A clean not-found is still Deleted. incremental_update_test now indexes the file before locking it and checks its nodes survive the unreadable Modified event; a mutant that erases indexed files on a failed check fails it. Drop the redundant <windows.h> block in daemon_server_test (unreadable_file.hpp already includes it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tgod009
approved these changes
Oct 21, 2026
tgod009
left a comment
There was a problem hiding this comment.
Independent review: NON-BLOCKING, ready to merge.
- All four
exists()calls on the enrichment-worker and serve-loop paths use theerror_codeoverload; the remaining throwing calls on those paths touch only the project root, never a file being saved. - Incremental path: an indexed file whose check fails is kept and republished, not erased; a never-indexed file is skipped. The watcher re-reports it when its token changes.
- The review's main SHOULD (a full rescan dropping a file whose check failed) is fixed in 30c58fe (Unreadable keeps the verified entry), and the incremental test now covers the indexed-file branch (a mutant that erases fails it).
read_sharedcloses its handle before returning, so removingpersisted.close()is safe.
Windows evidence: #211 ran cgraph_daemon_server_test 40/40 on each Windows job with 0 denied replaces; CI on 30c58fe is 12/12 green.
This was referenced Oct 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two Windows problems found while chasing a
windows-releaseflake incgraph_daemon_server_test("incremental state re-persisted to graph.json"). Evidence comes from a diagnostic branch (#211, to be closed) that repeated the test on the Windows runners with timing and Restart Manager logging.1. The daemon stopped on a file Windows reports as access denied (product bug)
std::filesystem::exists(path)throws when the path cannot be stat'ed, and Windows reports a file that is being replaced (an editor's save) as "Access is denied". Four call sites used the throwing form:file_cache.cppclassify_cached_fileandsemantic_chunk_plan.cppis_valid_cached_record: these run on the enrichment worker. In diag: Windows persist retry timing (do not merge) #211 the daemon stopped withenrichment refresh failed: std::filesystem::filesystem_error: exists: Access is denied.: "...\docs\guide.md". Since fix(cli): exit 1 instead of aborting on uncaught filesystem errors #179 that is a clean exit 1; before fix(cli): exit 1 instead of aborting on uncaught filesystem errors #179 it wasstd::terminate. In the test it showed up as a 600 s hang, because the test kept waiting on a daemon that had exited.incremental_update.cpp(two calls): these run on the serve loop for every watcher event, which is exactly when a file is mid-save.All four now use the
error_codeoverload. A file that cannot be checked counts as absent for that pass, as the neighbouringfile_size/last_write_timechecks already treat it, and the watcher reports it again once it is written.Tests:
file_cache_testandincremental_update_testreproduce the throw on POSIX with an unsearchable parent directory (existsthrowsstatus: Permission deniedthere). Both fail without the source change and pass with it.2. The flake itself was the test starving the daemon's replace (test only)
try_write_file_atomicallyalready retriesMoveFileExWfor 10 s while the target is held open. #211 showed the replace denied for longer than that, and Restart Manager named only the test process as the holder. The test polledgraph.jsonand the stat index withstd::ifstreamevery 20 ms, and MSVC opens withoutFILE_SHARE_DELETE. The test now reads those files throughread_shared, which on Windows opens withFILE_SHARE_DELETEso the replace goes through mid-read. POSIX is unchanged.Verification
cgraph_daemon_server_test3 repeats green.cgraph_daemon_server_testpassed 40/40 on windows-default and 40/40 on windows-release, with 0 denied replaces. Before the changes, the same harness failed on windows-release in both rounds (round 1 also hung on windows-default when the daemon stopped onexists: Access is denied).exists()now classifies asUnreadable, notDeleted, so a full rescan keeps a verified file's nodes.incremental_update_testindexes the file before locking it; a mutant that erases indexed files on a failed check fails it.🤖 Generated with Claude Code