Skip to content

feat(codes): introduce ERR_AUBE_/WARN_AUBE_ codes, exit codes, dep chains#492

Merged
jdx merged 7 commits intomainfrom
feat/error-codes
May 3, 2026
Merged

feat(codes): introduce ERR_AUBE_/WARN_AUBE_ codes, exit codes, dep chains#492
jdx merged 7 commits intomainfrom
feat/error-codes

Conversation

@jdx
Copy link
Copy Markdown
Contributor

@jdx jdx commented May 3, 2026

Summary

  • Every error and warning aube emits now carries a stable ERR_AUBE_* or WARN_AUBE_* identifier in a structured field, so CI scripts and ndjson consumers can branch on the code instead of substring-matching human messages.
  • A curated subset of error codes maps to bespoke Unix exit codes (10–99 in 10-wide ranges by category) so shell pipelines can react to specific failures without parsing stderr.
  • Post-resolver errors that mention a specific package now include the dependency chain back to the importer (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

  • New aube-codes crate — pure pub const &str definitions, EXIT_TABLE lookup, no runtime deps. All other crates depend on it.
  • Every tracing::warn! / tracing::error! site carries code = aube_codes::warnings::WARN_AUBE_X or errors::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.
  • Every thiserror::Error variant that's not a generic wrapper (Io, Http, Xx) gets #[diagnostic(code(ERR_AUBE_...))]. The resolver's manual Diagnostic impl gains a code() method alongside its existing help().
  • Free-standing miette! / bail! sites pass code = ... directly via miette's structured-field syntax.
  • crates/aube/src/main.rs wraps inner_main and looks up the failing diagnostic's code() against aube_codes::exit::EXIT_TABLE on the failure path. Codes outside the table fall back to EXIT_GENERIC (1).
  • crates/aube/src/dep_chain.rs builds an (name, version) → ancestors index from the resolved LockfileGraph (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.md lists every code with its description, exit code (when non-default), and category. Linked from the sidebar.

What's deliberately not in here

  • Custom field formatter that filters code out of text-mode output: skipped because the existing default formatter already prints structured fields and aube's existing warnings already use them, so the additional code = WARN_AUBE_X suffix is consistent with prior UX. ndjson consumers see proper JSON keys via the existing .json().flatten_event(true) layer.
  • Chain wrapping for every post-resolver error — the dep_chain module 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 to format_chain_for(name, version) once the install chain index is set.
  • ERR_PNPM_* aliases — explicitly out of scope per maintainer preference; aube uses ERR_AUBE_* exclusively.

Test plan

  • cargo build --workspace clean
  • cargo test --workspace --lib — 952 tests pass (8 in aube-codes cover code-name/value invariants, exit-code uniqueness + range, table cross-reference vs errors::ALL)
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo fmt --check clean
  • mise run test:bats — CI will exercise
  • Manual: trigger an unreviewed-build install and confirm --reporter ndjson emits "code": "WARN_AUBE_IGNORED_BUILD_SCRIPTS" plus the structured count and packages fields.
  • Manual: trigger ERR_AUBE_NO_LOCKFILE (e.g. aube install --frozen-lockfile in an empty dir) and confirm the process exits with code 10.

🤖 Generated with Claude Code


Note

Medium Risk
Touches error/warning emission across multiple crates and changes main to select bespoke process exit codes based on diagnostic code(), which can affect automation and failure behavior.

Overview
Introduces a new aube-codes workspace crate that centralizes stable ERR_AUBE_* / WARN_AUBE_* identifiers plus metadata and an exit-code lookup.

Wires these codes through the codebase: tracing::warn!/error! events gain a structured code field, and many thiserror enums now derive miette::Diagnostic with #[diagnostic(code(...))] so diagnostics consistently carry identifiers.

Updates the CLI failure path so crates/aube/src/main.rs renders the miette report and then exits with a bespoke Unix exit code when the diagnostic’s code() is in the table (otherwise 1). Adds dep_chain indexing 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.

…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.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ 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.

Comment thread crates/aube-registry/src/client.rs Outdated
Comment thread crates/aube-registry/src/client.rs
Comment thread crates/aube/src/commands/install/mod.rs
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 3, 2026

Greptile Summary

This PR introduces a new aube-codes crate with stable ERR_AUBE_*/WARN_AUBE_* identifiers, wires them through all error and warning sites, adds bespoke Unix exit codes (10–99 by category), and builds a BFS dependency-chain index so post-resolver errors can show the importer ancestry.

The structural foundation (aube-codes, dep_chain, exit-table, docs generator) is solid and well-tested. Three previously-flagged issues remain open: duplicate code fields in several tracing::warn! call sites in client.rs, format_chain_for using display_name instead of registry_name in the tarball-fetch error paths in mod.rs, and report_exit_code in main.rs reading only the outermost diagnostic code so errors wrapped in a plain miette!() lose their inner ERR_AUBE_* code — causing bespoke exit codes 30 (ERR_AUBE_TARBALL_INTEGRITY) and 32 (ERR_AUBE_PKG_CONTENT_MISMATCH) to always fall back to 1.

Confidence Score: 3/5

Multiple 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

Filename Overview
crates/aube-codes/src/lib.rs New crate entry point; CodeMeta struct, tests for prefix/uniqueness, and warnings registry validation look correct
crates/aube-codes/src/errors.rs All ERR_AUBE_* constants defined with matching string values; ALL registry covers every const; exit-code range and uniqueness tests pass
crates/aube-codes/src/exit.rs Linear-scan exit_code_for backed by errors::ALL; uniqueness + range tests are thorough
crates/aube/src/dep_chain.rs BFS chain index correct for standard cases; aliased-package mirror key can short-circuit BFS for a real same-name entry but practical impact is benign since both entries share deps
crates/aube/src/main.rs report_exit_code only reads the outermost diagnostic code() — errors wrapped in plain miette!() lose their inner codes, so bespoke exit codes for tarball/store errors will fall back to 1 (previously flagged)
crates/aube/src/commands/install/lifecycle.rs import_verified_tarball wraps aube_store::verify_integrity and validate_pkg_content errors in plain miette!() — diagnostic codes ERR_AUBE_TARBALL_INTEGRITY (exit 30) and ERR_AUBE_PKG_CONTENT_MISMATCH (exit 32) are stripped (previously flagged)
crates/aube/src/commands/install/mod.rs format_chain_for fetch-error calls use display_name instead of registry_name — silently returns empty chain for aliased packages (previously flagged)
crates/aube-codes/src/warnings.rs All WARN_AUBE_* constants defined with matching values; ALL registry is complete; no exit codes on warnings as expected
crates/aube-registry/src/client.rs Duplicate code field in several tracing::warn! calls — ndjson consumers may see wrong or rejected events (previously flagged)
crates/aube-codes/src/bin/generate_error_codes_docs.rs Docs generator emits errors::ALL + warnings::ALL as JSON; workspace root derivation via CARGO_MANIFEST_DIR traversal is correct

Reviews (5): Last reviewed commit: "fix(codes): drop unused aube-codes dep f..." | Re-trigger Greptile

Comment thread crates/aube-registry/src/client.rs Outdated
Comment thread crates/aube/src/commands/install/mod.rs
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 3, 2026

Benchmark changes

Public ratios: warm installs vs Bun 7x -> 10x; warm installs vs pnpm 8x -> 13x.

Benchmark aube bun pnpm
Fresh install (warm cache) 304ms -> 203ms (-33%) 2064ms -> 1942ms (-6%) 2518ms -> 2656ms (+5%)
CI install (warm cache, GVS disabled) 1448ms -> 747ms (-48%) 2789ms -> 2028ms (-27%) 2356ms -> 2455ms (+4%)
CI install (cold cache, GVS disabled) 4887ms -> 4056ms (-17%) 4735ms -> 4006ms (-15%) 5812ms -> 5020ms (-14%)

58cede0 vs 9eb7b96 | aube/bun/pnpm | 3 scenarios | 3 runs | 500mbit/50ms | generated by Codex.

jdx added 6 commits May 3, 2026 13:55
* `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.
@jdx jdx merged commit 138d17c into main May 3, 2026
18 checks passed
@jdx jdx deleted the feat/error-codes branch May 3, 2026 14:40
@greptile-apps greptile-apps Bot mentioned this pull request May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant