Conversation
…losed one instance of (#363) An unreadable parent directory made check_directory, check_jit_rules and resolve_project_dir's own --root check swallow to a confident "does not exist"/"no rules for this repo", with a remedy telling the reader to create something that may already be there -- #341's own sentence one call site over. Those three now go through a shared _dir_state(path) that returns a third "unreadable" answer instead of folding it into absence, and resolve_project_dir -- the entry point where the third-state contract is established, not merely consumed -- gets the same treatment for its own .git existence check. merge_permission_state's path.exists() now routes an unreadable settings candidate into the "unknown" bucket the function already had and never reached. The two narrow glob filters (jit_hook_roots, _jit_layer_verdict) now use a swallow-only _safe_is_dir so one unreadable candidate no longer wipes every candidate already found in the same scan, mirroring the swallow-vs-raise fix #359 made for _safe_is_file. Every site is covered by an injected, version-independent test plus a real chmod-based fixture where the platform's own permission model can produce it, each with a "must not fire" control confirming a genuine absence still reports as absent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9R2KfyZyHEXhMrE1VcNwH
…the exact interpreter this fix was for (#363) A spawned audit (second pass, after the first pass's return went unstated and was re-run) caught that `_dir_state` classified "unreadable" by catching `OSError` out of `Path.is_dir()`. Measured directly on this machine's local Python 3.14 install: `is_dir()` against the exact unreadable-parent fixture this PR's own tests build returns `False` with no exception at all, so the `except OSError` branch was unreachable there and `check_directory`/`check_jit_rules`/`resolve_project_dir` would have silently degraded back into the confident-absence bug #363 exists to fix -- on precisely the interpreter CLAUDE.md already names as the one that swallows, and that the real chmod-based test in this same PR was quietly self-skipping on for that reason, with a skip message that blamed "this platform/filesystem" rather than the interpreter. `_dir_state` now calls `path.stat()` instead, which does raise on 3.14 (also measured directly). `stat()` also raises `FileNotFoundError`/ `NotADirectoryError` for a genuinely absent path, which the first version of this change folded into "unreadable" and broke -- caught immediately by this file's own must-not-fire controls, which is the reason they exist. Both exception types are caught ahead of the general `OSError` arm by type, not by an errno table CLAUDE.md already warns Windows folds unevenly. The three injected tests that target `_dir_state`'s consumers now patch `Path.stat` instead of `Path.is_dir`, matching what the code actually calls; the real chmod-based fixture now probes `stat()` too and no longer self-skips on this machine -- it exercises the exact case it was written for instead of quietly not doing so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9R2KfyZyHEXhMrE1VcNwH
…comments (#363) Spawned reviewer finding: the section-header comments above each group of tests cited pre-fix line numbers (the sweep issue's own approximate pointers), and the fix shifted every call site downward by 40-80 lines adding the new _dir_state/_safe_is_dir helpers. Not load-bearing -- the tests call the right functions by name regardless -- but a stale line number next to working code is exactly the kind of thing nobody re-checks. Named by function instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9R2KfyZyHEXhMrE1VcNwH
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.
An unreadable parent directory made
check_directory,check_jit_rulesandresolve_project_dir's own--rootcheck swallow to a confident "does not exist"/"no rules for this repo", with a remedy telling the reader to create something that may already be there -- #341's own sentence one call site over, six more times (filed as #363's sweep). Fixes #363.What changed
_safe_is_dir(path)(swallow-only, mirrors the existing_safe_is_file) for callers that only filter a list, and_dir_state(path)-- a genuine three-state classifier (dir/absent/unreadable) for callers that print a verdict -- near the existing_safe_is_filehelper.check_directory,check_jit_rules, andresolve_project_dir's ownchosencheck (the entry point where doctor's third-state contract is established, not merely consumed) now use_dir_stateinstead of a bareis_dir(), replacing a confident absence with a distinct "could not be checked" WARN.resolve_project_dir's.gitexistence check is now wrapped in its own try/except OSError.merge_permission_state'spath.exists()now routes an unreadable settings candidate into theunknownbucket the function already had and never reached.jit_hook_roots,_jit_layer_verdict) now use_safe_is_dirso one unreadable candidate no longer wipes every candidate already found via the outerexcept OSError.The self-review round that mattered
The first version of
_dir_stateclassified "unreadable" by catchingOSErrorout ofPath.is_dir(). A spawned audit (second pass, after the first pass returned a header-less, partially-unstated message and was re-run once per the developer brief) measured directly that on this machine's local Python 3.14,Path.is_dir()itself swallows thePermissionErroran unreadable parent produces and returnsFalse-- so theexcept OSErrorbranch was unreachable on precisely the interpreter CLAUDE.md already names as the one that swallows, and the fix would have silently degraded back into the exact bug #363 exists to close, invisible to CI (which gates 3.9-3.12 only)._dir_statenow callspath.stat()instead (measured to raise on the same fixture on 3.14), withFileNotFoundError/NotADirectoryErrorcaught ahead of the generalOSErrorarm so a genuinely absent path isn't folded into "unreadable" -- a regression the first attempt at this fix introduced and this file's own must-not-fire controls caught before commit. The real chmod-based test in the suite, which had been silently self-skipping on this machine with a message blaming "this platform/filesystem", now runs and passes -- it exercises the case it was written for instead of quietly not doing so.Testing
Every site: an injected, version-independent monkeypatch test, paired with a "must not fire" control confirming genuine absence still reports as absent. Two sites also get a real chmod-based fixture that self-skips loudly with what went untested when the platform's permission model doesn't produce the effect (root, some filesystems, Windows' read-only attribute not blocking a listing) -- and, after the self-review fix, no longer skips on this machine's 3.14.
doctor.py'sexit 0 always, one VERDICT linecontract verified unchanged on every new path.Full suite: 2587 passed, 2 skipped (unrelated), 0 failed.
Closes #363.