fix(integrations): guard _sha256 against unreadable managed files#3376
Open
jawwad-ali wants to merge 1 commit into
Open
fix(integrations): guard _sha256 against unreadable managed files#3376jawwad-ali wants to merge 1 commit into
jawwad-ali wants to merge 1 commit into
Conversation
manifest.py::_sha256 does an unguarded open(). check_modified() and uninstall() both call it on a readable-but-unopenable regular file (e.g. permission denied) without catching OSError, so 'specify integration upgrade/uninstall/switch' surface a raw PermissionError traceback. Guard both call sites: in check_modified() treat an unreadable file as modified (consistent with the adjacent symlink / non-regular-file handling); in uninstall() treat it as skipped and preserve it (mirroring the existing path.unlink() OSError guard just below). The force short-circuit is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens IntegrationManifest’s hash-based change detection and uninstall logic so unreadable-but-existing managed files (e.g., permission denied on open()) don’t crash integration workflows with raw tracebacks.
Changes:
check_modified(): catchesOSErrorfrom_sha256()and treats unreadable regular files as modified.uninstall(force=False): catchesOSErrorfrom_sha256()and preserves unreadable files by returning them inskipped.- Adds deterministic, cross-platform tests that simulate unreadable files by monkeypatching
_sha256to raisePermissionError.
Show a summary per file
| File | Description |
|---|---|
| src/specify_cli/integrations/manifest.py | Adds OSError guards around _sha256() in check_modified() and uninstall() to avoid tracebacks and preserve safety semantics. |
| tests/integrations/test_manifest.py | Adds tests verifying unreadable managed files are treated as modified and are preserved (skipped) on uninstall when not forced. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Low
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.
Description
manifest.py::_sha256does an unguardedopen(path, "rb"). Two callers invoke it on a regular file that exists but can't be opened (e.g. permission denied) without catchingOSError:check_modified()(line ~312) — only symlinks/non-regular files are guarded above; a readable-looking-but-unopenable regular file lets theOSErrorescape.uninstall()(line ~361) — the siblingpath.unlink()right below is wrapped inexcept OSError, but the hash read is not (asymmetric).So
specify integration upgrade/uninstall/switchdump a rawPermissionErrortraceback instead of handling it. Reproduced on main @92b7cf7.Fix (single file —
manifest.py)check_modified(): catchOSErrorand treat an unreadable file as modified, consistent with how symlinks / non-regular files are already appended just above.uninstall(): catchOSErroraround the hash read and treat it as skipped/preserved (can't verify ownership → don't delete), mirroring the existingunlink()OSErrorguard. Theforceshort-circuit is unchanged.Testing
New
TestManifestUnreadableFile— deterministic and cross-platform viamonkeypatchof_sha256to raisePermissionError(no realchmod/symlink needed):check_modified()returns the file as modified (was: raised);uninstall(force=False)returns it inskipped, removes nothing, leaves it on disk (was: raised).Both fail-before / pass-after (verified by source-stash). Note: 6 pre-existing symlink tests in this file fail locally on Windows (symlink privilege) — identical on clean main, unrelated to this change, and green on Linux CI.
AI Disclosure
Found and fixed with Claude Code (Claude Fable 5) under my direction. AI flagged the unguarded
_sha256against the guardedunlink()sibling; I reproduced the traceback path, wrote cross-platform monkeypatch tests, verified fail-before/pass-after, and confirmed the 6 remaining failures are pre-existing Windows-symlink environment issues.