feat(codes): introduce ERR_AUBE_/WARN_AUBE_ codes, exit codes, dep chains#492
feat(codes): introduce ERR_AUBE_/WARN_AUBE_ codes, exit codes, dep chains#492
Conversation
…ains Every error and warning aube emits now carries a stable `ERR_AUBE_*` or `WARN_AUBE_*` identifier so consumers (CI, ndjson reporters, third-party tooling) can branch on machine-readable codes instead of regex-matching human messages. A curated subset of error codes is mapped to bespoke Unix exit codes; post-resolver errors that mention a specific package now include the dependency chain back to the importer. * New `aube-codes` crate exports every code as `pub const &str` plus an `EXIT_TABLE` mapping to bespoke exit codes (10–99 in 10-wide ranges by category). Self-tests reject typos, duplicates, and out-of-range exits. * All `tracing::warn!` / `tracing::error!` sites attach `code = ...` as a structured field. Retry-loop warnings collapse to four template-level codes since per-call-site codes for those would be log noise. * Every `thiserror::Error` enum variant that's not a generic wrapper (`Io`, `Http`, `Xx`) gets a `#[diagnostic(code(ERR_AUBE_...))]`. The resolver's manual `Diagnostic` impl gains a `code()` method. Free-standing `miette!` / `bail!` sites pass `code = ...` directly. * `crates/aube/src/main.rs` looks up the diagnostic's `code()` against the exit table on the failure path; missing entries fall back to 1. * `crates/aube/src/dep_chain.rs` builds an `(name, version) → ancestors` index from the resolved `LockfileGraph` and stashes it process-globally. Tarball-fetch / integrity / store-import / pkg-content sites consult it and append `chain: a@1 > b@2 > leaf@3` to their messages. * `docs/error-codes.md` documents every code with its description and exit code, plus the naming convention and a guide for adding new codes.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4f1331f. Configure here.
Greptile SummaryThis PR introduces a new The structural foundation ( Confidence Score: 3/5Multiple P1 issues remain open from prior review rounds; bespoke exit codes for tarball/store errors will always fall back to 1 at runtime Score reflects three previously-flagged P1s that are still present in the diff: duplicate tracing fields, alias-vs-real-name chain lookup mismatch, and the outermost-code-only exit lookup. Each independently defeats a core deliverable of this PR. crates/aube/src/main.rs, crates/aube/src/commands/install/lifecycle.rs, crates/aube/src/commands/install/mod.rs, crates/aube-registry/src/client.rs Important Files Changed
Reviews (5): Last reviewed commit: "fix(codes): drop unused aube-codes dep f..." | Re-trigger Greptile |
Benchmark changesPublic ratios: warm installs vs Bun 7x -> 10x; warm installs vs pnpm 8x -> 13x.
58cede0 vs 9eb7b96 | aube/bun/pnpm | 3 scenarios | 3 runs | 500mbit/50ms | generated by Codex. |
* `aube-registry/src/client.rs`: drop the duplicate `code = WARN_AUBE_HTTP_RETRY_BODY_DECODE` field at the four sites where my earlier replace_all matched indent-25 patterns inside indent-33 occurrences. Structured (ndjson) consumers no longer see two `code` keys in the same record. * `aube/src/dep_chain.rs`: index aliased entries under both their alias name (`pkg.name`) and their real npm name (`pkg.alias_of`) so `lifecycle.rs` (looks up by `registry_name`) and `mod.rs` (looks up by `display_name`) both resolve aliased packages. New test: `aliased_packages_resolve_under_both_alias_and_real_name`. * `aube/src/commands/install/mod.rs`: thread `(pkg.name, pkg.version)` into `import_local_source` and append the chain to its `miette!` / `wrap_err_with` sites so file:/link:/git:/remote-tarball deps surface their pull path too. Adds `#[allow(clippy::too_many_arguments)]`. * `aube/src/commands/install/lifecycle.rs`: append chain to the two `wrap_err_with` calls that parse a dep's `package.json` so allow-builds review and lifecycle-script discovery errors include the chain. * `.rules`: add the new `aube-codes` crate to the crate roster, document the error/warning code convention, exit-code allocation, dep-chain plumbing, and the four-step "adding a code" recipe so future agents follow the convention without re-deriving it.
Mirrors the settings-doc gen pattern. Each code's category,
description, and (for errors) bespoke exit code now lives next to
its `pub const` declaration in `crates/aube-codes/src/{errors,warnings}.rs`
as part of a `CodeMeta` entry in `ALL` — one source of truth instead
of two (registry + handwritten markdown). The doc page becomes a
build artifact regenerated on every `mise run render`, and a
self-test gates that every code carries a non-empty description.
* Lift `ALL` from `&[(&str, &str)]` to `&[CodeMeta]` carrying name,
category, description, exit_code (None for warnings, Option<i32>
for errors). The `pub const ERR_AUBE_*` / `pub const WARN_AUBE_*`
declarations stay (call sites consume them); `ALL` references the
same constants so a rename flows through both with no drift.
* Collapse `EXIT_TABLE` into the same registry — `exit::exit_code_for`
now walks `errors::ALL`. Tests still gate uniqueness, [10, 125]
range, and round-trip; the duplicate `EXIT_TABLE` constant is gone.
* New `crates/aube-codes/src/bin/generate_error_codes_docs.rs` walks
the registries, groups by category in declaration order, and writes
`docs/error-codes.md`. Wired into `mise run render` after the
settings doc gen.
* Self-tests now reject codes with empty descriptions or empty
categories, and reject any warning that accidentally got an
`exit_code` set (warnings don't change exit status).
rustfmt's default 100-col width wraps long const lines into a two-line `name: &str =\n "name";` shape that breaks the visual rhythm of the flat const list. Skipping fmt on the five affected entries keeps them one line each so the file scans as a single declaration table.
Mirrors the benchmarks pattern (`benchmarks/results.json` → `docs/benchmarks.data.ts` → `<BenchChart>`). The hand-rolled markdown table was static and ungrep-friendly past ~85 entries; an interactive Vue component lets users search by name/description and filter by category. * `generate-error-codes-docs` now emits `docs/error-codes.data.json` instead of markdown. Schema documented inline; hand-rolled JSON emitter so `aube-codes` stays dep-free. * `docs/error-codes.data.ts` — VitePress build-time data loader, same shape as `docs/benchmarks.data.ts`. * `docs/.vitepress/theme/ErrorCodesTable.vue` — searchable + category-filterable table with sticky controls. Two mounts on `/error-codes` (one for errors, one for warnings) keep their own filter state. Inline-code is rendered from backticks in the registry's description strings via a small one-pass substitution. * `docs/error-codes.md` is now hand-written prose + two component mounts; it's no longer a generated artifact. Only the underlying JSON is regenerated from the registry. * `docs/package.json` adds `error-codes:gen` and chains it into `data:gen` so `npm run docs:dev` and `docs:build` always rebuild the JSON before VitePress picks it up.
The dep-free posture was overly precious — `serde_json` is already in 8 of 10 aube crates, so adding it to `aube-codes` doesn't grow the compile graph. `CodeMeta` derives `Serialize` and the generator hands the registry plus the first-seen-order category lists straight to `serde_json::to_string_pretty`. Less code, no escape edge cases, and the schema travels with the type definition.
cargo-machete on the test-linux job correctly flagged `aube-codes` as unused in `aube-scripts`, `aube-workspace`, and `aube-store`. These crates only mention codes via `#[diagnostic(code(ERR_AUBE_X))]` attributes, where miette treats the `code(...)` argument as an identifier token rather than a symbol reference, so the dependency never actually resolves a symbol from `aube-codes`. Drop the dep in all three Cargo.tomls and leave a comment in each explaining why the codes are still legal but the crate-level dep isn't needed. Validation that diagnostic-attribute names exist in `aube_codes::errors::ALL` is a workspace-wide concern, not something each consumer should pretend to enforce via a dummy dep declaration. Verified locally: `cargo machete` is now clean.

Summary
ERR_AUBE_*orWARN_AUBE_*identifier in a structured field, so CI scripts and ndjson consumers can branch on the code instead of substring-matching human messages.chain: a@1 > b@2 > leaf@3), so a tarball-integrity or fetch failure tells the user why their install pulled that transitive dep.Motivated by discussion #9537 on the mise side: a downstream consumer wrapping aube needed a stable way to detect the unreviewed-build-scripts warning without text matching.
What's in here
aube-codescrate — purepub const &strdefinitions,EXIT_TABLElookup, no runtime deps. All other crates depend on it.tracing::warn!/tracing::error!site carriescode = aube_codes::warnings::WARN_AUBE_Xorerrors::ERR_AUBE_X. Retry-loop warnings collapse to four template-level codes (transient, transport, body-read, body-decode); ~25 call sites share those four codes since per-site codes would be operational noise.thiserror::Errorvariant that's not a generic wrapper (Io,Http,Xx) gets#[diagnostic(code(ERR_AUBE_...))]. The resolver's manualDiagnosticimpl gains acode()method alongside its existinghelp().miette!/bail!sites passcode = ...directly via miette's structured-field syntax.crates/aube/src/main.rswrapsinner_mainand looks up the failing diagnostic'scode()againstaube_codes::exit::EXIT_TABLEon the failure path. Codes outside the table fall back toEXIT_GENERIC(1).crates/aube/src/dep_chain.rsbuilds an(name, version) → ancestorsindex from the resolvedLockfileGraph(BFS; first-write-wins = shortest chain) and stashes it process-globally. The most prominent post-resolver error sites (tarball fetch, integrity verification, store import,strictStorePkgContentCheck) consult it and append the chain to their messages.docs/error-codes.mdlists every code with its description, exit code (when non-default), and category. Linked from the sidebar.What's deliberately not in here
codeout of text-mode output: skipped because the existing default formatter already prints structured fields and aube's existing warnings already use them, so the additionalcode = WARN_AUBE_Xsuffix is consistent with prior UX. ndjson consumers see proper JSON keys via the existing.json().flatten_event(true)layer.dep_chainmodule is wired into the highest-traffic sites (tarball fetch + import + integrity + content check). The remaining post-resolver call sites (linker, scripts, patch) can adopt incrementally — they have access toformat_chain_for(name, version)once the install chain index is set.ERR_PNPM_*aliases — explicitly out of scope per maintainer preference; aube usesERR_AUBE_*exclusively.Test plan
cargo build --workspacecleancargo test --workspace --lib— 952 tests pass (8 inaube-codescover code-name/value invariants, exit-code uniqueness + range, table cross-reference vserrors::ALL)cargo clippy --workspace --all-targets -- -D warningscleancargo fmt --checkcleanmise run test:bats— CI will exercise--reporter ndjsonemits"code": "WARN_AUBE_IGNORED_BUILD_SCRIPTS"plus the structuredcountandpackagesfields.ERR_AUBE_NO_LOCKFILE(e.g.aube install --frozen-lockfilein an empty dir) and confirm the process exits with code10.🤖 Generated with Claude Code
Note
Medium Risk
Touches error/warning emission across multiple crates and changes
mainto select bespoke process exit codes based on diagnosticcode(), which can affect automation and failure behavior.Overview
Introduces a new
aube-codesworkspace crate that centralizes stableERR_AUBE_*/WARN_AUBE_*identifiers plus metadata and an exit-code lookup.Wires these codes through the codebase:
tracing::warn!/error!events gain a structuredcodefield, and manythiserrorenums now derivemiette::Diagnosticwith#[diagnostic(code(...))]so diagnostics consistently carry identifiers.Updates the CLI failure path so
crates/aube/src/main.rsrenders themiettereport and then exits with a bespoke Unix exit code when the diagnostic’scode()is in the table (otherwise1). Addsdep_chainindexing seeded after resolution and appends dependency-chain context to key post-resolver install errors (fetch/import/integrity/content-check and local-source import) to explain why a package was pulled in.Reviewed by Cursor Bugbot for commit 58cede0. Bugbot is set up for automated code reviews on this repo. Configure here.