feat: honest error reporting, prune-skip summary, extraction fixes - #80
Merged
Conversation
Introduce ColliderUserError carrying an os.EX_* exit code, handled at the entrypoint so usage and environment problems no longer surface as the internal-bug banner with exit 1. - FileModelInterface.from_path maps parse/validation/deserialization failures to EX_DATAERR and read errors to EX_IOERR, fixing every subcommand that loads collider.json or collider.lock in one place. - Setup (missing "--" separator) and pkg search (invalid regex) exit EX_USAGE cleanly. - remove_installed_artifacts raises EX_IOERR on undeletable artifacts; extracted trees are removed before the wrap so a failed removal stays retryable. - Add/Upgrade/Install install blocks handle OSError instead of only FileNotFoundError, and unreadable wrap files no longer crash pkg info or pkg upgrade. - managed_package_names and detect_locked_wrap_drift stop wrapping lock read errors into a misleading "malformed" ValueError. - status logs a warning when version resolution fails instead of silently listing transitive wraps as untracked. Known exemption: the TOCTOU FileNotFoundError race (lock deleted between exists() and open) still routes to the bug handler; the window is negligible and callers guard with exists().
When remove --prune or prune cannot run because collider.lock is absent or unreadable, the command now ends with a single greppable line: 'prune skipped: <reason>; run "collider lock".' Exit code stays EX_OK, since pruning a project that legitimately has no lock is valid. The lockfile-refresh warning in remove moved ahead of the prune step so the summary is always the last log line.
- Pass tarfile's filter kwarg only when the PEP 706 backport is present (hasattr tarfile.data_filter); Python 3.10.0-3.10.11 and 3.11.0-3.11.3 raised TypeError on every tar extraction otherwise. - Treat an unreadable or corrupt cached releases.json as a cache miss: within-TTL corruption refetches from the network, and the network-failure fallback re-raises the network error instead of masking it with a JSON parse crash. - Drop Requirement/Candidate custom __eq__/__hash__: resolvelib identifies requirements via identify() (name), never by hash, and the frozen dataclass equality is field-complete; same-name requirements with different constraints no longer collapse in sets. - Use errno.EXDEV over the literal 18 and missing_ok on the corrupt archive unlink.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
- Validate WrapDB releases.json is a JSON object after fetch; raise ColliderUserError(EX_DATAERR) instead of an opaque RuntimeError so a null/non-dict 200 body is reported honestly. - Extend the final guard to cover non-dict cached/offline releases. - Add contract tests asserting install returns EX_IOERR when cache preparation or wrap-file writing fails with a permission error. - Add a test for the stale-cache fallback warning and for the null-body data error path.
MaxandreOgeret
force-pushed
the
feat/honest-user-errors
branch
from
July 2, 2026 14:00
7be40c7 to
abfecf4
Compare
- Repo: re-raise ColliderUserError from config load so the carried exit code reaches the entrypoint instead of being flattened to EX_DATAERR. - Prune: return the carried exit code on a corrupt lockfile so direct `collider pkg prune` fails honestly instead of exiting EX_OK. - Wrap: offline corrupt/missing cache raises ColliderUserError(EX_DATAERR) instead of ValueError, matching the non-object cache path; use bare raise. - resolver: narrow the scan-failure catch to FileNotFoundError so real permission or disk-space errors propagate rather than degrading to an empty dependency list. - project_state: tolerate a lockfile vanishing between exists() and open(). - errors: guard ColliderUserError against a zero exit code. - Info: map UnicodeDecodeError to EX_DATAERR and OSError to EX_IOERR. - config: keep skipping a repo that fails to load so one bad repo does not abort the whole CLI; the warning remains the user-facing signal.
MaxandreOgeret
force-pushed
the
feat/honest-user-errors
branch
from
July 2, 2026 14:57
1f595fa to
1d78a0a
Compare
A PermissionError or ENOSPC while staging a source for dependency scanning propagated bare through resolvelib into install/lock/add, whose handlers catch only resolver exceptions, and surfaced as the internal-bug banner. Map it to ColliderUserError(EX_IOERR) at the scan site so strict callers exit cleanly while best-effort callers keep degrading gracefully. Also name the cache file in the malformed cached-releases error and document why remove --prune ignores run_prune's exit code (issue #46).
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.
Summary
Fixes surfaced by a full adversarial code review of the repository.
ColliderUserError(message, exit_code)handled at the entrypoint: usage and user-environment failures (malformed/unreadablecollider.json/collider.lock, undeletable artifacts, invalid regex, missing--separator) now exit with accurateos.EX_*codes and a clean message instead of the "probably a bug within collider" banner.EX_IOERR) instead of "malformed", and the colliderfile fallback catch is narrowed. Closes project_state: narrow broad except in managed_package_names (read errors mislabeled, colliderfile swallowed) #52remove --prune/pruneend skipped runs with a trailing greppable summary line, exit code unchanged (EX_OK). Closes pkg remove --prune: print end-of-run summary when prune is skipped (no lockfile) #46filter=backport; corrupt cachedreleases.jsonis treated as a cache miss on all paths;statuslogs a warning when resolution fails instead of silently mislabeling transitive wraps; dead name-onlyRequirement.__eq__/__hash__removed.Known exemption: the TOCTOU
FileNotFoundErrorrace named in #52 (lock deleted betweenexists()andopen) still routes to the bug handler; the window is negligible and all callers guard withexists(). Diagnostic follow-up for CRITICAL-level logs on best-effort paths: #79.Type of change
Checklist
uv run pytest).uv run ruff format --check .,uv run ruff check .,uv run ty check collider,uv run pylint --rcfile=pyproject.toml ./collider).AI disclosure (Tick all that apply)