refactor(core): preserve error sources and print the cause chain#1653
Merged
Conversation
This was referenced Jun 19, 2026
Contributor
Merge Protections🟢 All 5 merge protections satisfied — ready to merge. Show 5 satisfied protections🟢 🤖 Continuous Integration
🟢 👀 Review Requirements
🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
🟢 🔎 Reviews
🟢 📕 PR description
|
This was referenced Jun 19, 2026
Contributor
Author
|
This pull request is part of a Mergify stack:
|
This was referenced Jun 19, 2026
c583cb3 to
9454772
Compare
8738916 to
6ba7b33
Compare
Contributor
Author
Revision history
|
6ba7b33 to
6dbc5f9
Compare
9454772 to
3dbd4b7
Compare
Base automatically changed from
devs/JulianMaurin/feat/rust-cli-excellence/model-stack-native-clap-subcommand-tree--ccded108
to
main
June 22, 2026 12:49
Every non-Io `CliError` variant was `Variant(String)`, so a wrapped
cause could only survive by being flattened into the message with
`format!("...: {e}")` — `source()` was always `None` and a typed
lower-level error (e.g. `InvalidJunitXml`, `QuarantineFailed`) was
collapsed via `.to_string()`, losing its type.
Give `CliError` two ways to keep a cause:
- `Source(#[from] Box<dyn Error + Send + Sync>)` — transparent: same
Display, preserves the typed error as a downcastable, chainable
source. `From<InvalidJunitXml>` / `From<QuarantineFailed>` now use
it instead of `.to_string()` (output unchanged — both are
self-describing leaves).
- `Wrapped { context, #[source] source }` via `CliError::wrap(ctx, e)`
— a context headline plus a preserved cause, for "doing X failed
because of Y".
`main()` now walks `source()` and prints each cause as a
`caused by:` line. `Io` drops its `#[from]` (kept as a manual `From`)
so its full-message Display isn't also exposed as a source and
double-printed; it stays a self-contained leaf.
This is the idiomatic foundation; commands adopt `wrap()` for their
network/IO failures in later commits.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Change-Id: I21542b1cd0b25214b9e197bc9b2228136a357dc4
3dbd4b7 to
7adfa85
Compare
remyduthu
approved these changes
Jun 22, 2026
kozlek
approved these changes
Jun 22, 2026
Contributor
|
Queued — the merge queue status continues in this comment ↓. |
Contributor
Merge Queue Status
This pull request spent 11 seconds in the queue, including 2 seconds running CI. Required conditions to merge
|
38 tasks
mergify Bot
pushed a commit
that referenced
this pull request
Jun 22, 2026
…1654) `mergify self-update` mapped every failure — including GitHub network, API, and release-integrity errors — to `CliError::Generic` (exit 1), so a flaky-GitHub update was indistinguishable from a local bug and wrapping scripts got the wrong exit code. Classify GitHub failures as `CliError::GitHubApi` (exit 5): the release metadata fetch, the asset/SHA256SUMS downloads, and the release-integrity checks (missing/malformed checksum entry, checksum mismatch). Purely-local failures (locating the binary, temp dirs, writing the archive, the atomic swap, spawning the extractor) stay `Generic`/`Io`. Add a 3-attempt retry with backoff around the network reads (`fetch_latest_release`, the asset and SHA256SUMS downloads), which are one-shot and idempotent, so a transient blip no longer fails the update. Integrity checks run outside the retry so a real mismatch fails fast. self-update keeps its own `reqwest` rather than `mergify_core::http`: that client is path/JSON/auth-oriented, while self-update does unauthenticated binary downloads across two hosts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Depends-On: #1653
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.
Every non-Io
CliErrorvariant wasVariant(String), so a wrappedcause could only survive by being flattened into the message with
format!("...: {e}")—source()was alwaysNoneand a typedlower-level error (e.g.
InvalidJunitXml,QuarantineFailed) wascollapsed via
.to_string(), losing its type.Give
CliErrortwo ways to keep a cause:Source(#[from] Box<dyn Error + Send + Sync>)— transparent: sameDisplay, preserves the typed error as a downcastable, chainable
source.
From<InvalidJunitXml>/From<QuarantineFailed>now useit instead of
.to_string()(output unchanged — both areself-describing leaves).
Wrapped { context, #[source] source }viaCliError::wrap(ctx, e)— a context headline plus a preserved cause, for "doing X failed
because of Y".
main()now walkssource()and prints each cause as acaused by:line.Iodrops its#[from](kept as a manualFrom)so its full-message Display isn't also exposed as a source and
double-printed; it stays a self-contained leaf.
This is the idiomatic foundation; commands adopt
wrap()for theirnetwork/IO failures in later commits.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com