ErrLookup › Background articles › eyre::Report: Rust bail!/ensure! errors like 'invalid tool path' and 'git failed with status' explained (mise)
eyre::Report: Rust bail!/ensure! errors like 'invalid tool path' and 'git failed with status' explained (mise)
eyre::Report is the error type Rust programs built on the eyre crate throw with bail! and ensure!, so you meet it as a one-line, self-describing message such as 'invalid tool path ... contains forbidden character' or 'archive does not contain registry entries' whenever an internal guard rejects a value, a download, or an environment before work continues. In mise, the best-documented source of this family, these errors mark shell-safety and path-traversal boundaries, corrupt caches and truncated downloads, stale lockfiles and plans, and platform limits like glibc-only Homebrew bottles. Because every message is written by hand at each call site, the message text itself is the diagnosis: it names the offending value and the expectation it failed.
Distilled from 408 documented records across 2 repositories.
Background
eyre::Report is the interchangeable error type of the eyre crate: Rust programs wrap arbitrary errors in it or, more often, throw it directly with bail!/ensure! at hundreds of discrete guard points. From the caller's side there is no error code and no structured payload to inspect — the report is a formatted prose line with the offending value interpolated directly into it, like 'invalid tool path {s:?}: contains forbidden character {c:?}' or 'cannot relocate {}: replacement for {} does not fit ({} > {} bytes)'. The searchable phrase in the message is the error's identity, which is why one family accumulates hundreds of distinct records (408 documented across two repositories) that all behave the same way at the boundary: the program stops at the guard and prints one sentence.
Most of these guards exist as deliberate fail-fast or security boundaries. mise rejects shell metacharacters (quotes, backtick, backslash, dollar, control characters, and on Windows the cmd.exe metacharacters & | < > ^ %) in tool versions and paths because those strings flow into install directory names and plugin hook command lines; it rejects absolute or parent-escaping bin/rename_exe values so a binary cannot resolve outside the install directory; it sanitizes OCI layer tar entries against .. and drive prefixes as a TarSlip guard; and it refuses an appdir that resolves to the filesystem root because that would silently disable path containment for privileged writes. Resource defenses appear in the same shape: a 16 MiB cap on command-input probe output and a 4096-entry cap on the registry archive as a decompression-bomb defense.
Beyond validation, the family covers integrity and state checks. Downloaded or cached bytes are verified against an expected shape (a registry archive must contain registry/*.toml entries and not more than 4096 of them; a Homebrew bottle must extract to <name>/<version>), git operations re-raise non-zero exits as 'git failed with status' or 'git -C {} {} failed', lockfile-pinned installs refuse tool versions missing from mise.lock, and plan executors abort when a target file changed between confirmation and execution — a deliberate time-of-check/time-of-use guard. Platform contracts round it out: glibc-requiring Homebrew bottles fail on musl distros, and Windows extended-length \\?\ prefixes are rejected because the normalization that rewrites backslashes to '/' cannot apply inside them.
Because each call site authors its own message, wording is library-specific and even guard-specific: the same underlying mistake (a pasted metacharacter, a truncated download) produces different text in different programs, and within one program the same failure can be fatal on one path and downgraded on another — mise turns registry parse failures into a warning with a baked-in fallback in normal startup, and several git query failures are swallowed with .ok(). The depth of documentation in this family comes from mise; when messages differ between the two repositories in the family, treat the phrasing, not the mechanism, as the library-specific part.
Common causes
- Config values that fail validation guards.Tool versions, paths, refs, bin and rename_exe options containing shell metacharacters, absolute paths, '..' segments, path separators, or a leading dash. These are rejected at parse or option-read time, before any install or network work happens.
- Corrupt or wrong bytes in downloads and caches.A registry archive with zero (or more than 4096) qualifying entries, an unexpected bottle layout after extraction, or a cached artifact that is really an HTML error page saved with a 200 status. Proxies, captive portals, truncated writes, and full disks are the usual sources.
- Network and credential failures surfacing through git.clone, fetch, ls-remote, or pull --ff-only exiting non-zero, re-raised as 'git failed with status {status}' or 'git -C {} {} failed'. Auth denials, unreachable hosts, diverged local branches, and corrupt indexes are concrete examples.
- Platform and environment incompatibilities.Homebrew bottles requiring a glibc dynamic loader on musl-based distros, Windows extended-length \\?\ and \.\ prefixes, cmd.exe metacharacters in tool paths, and paths too long to fit fixed-size bottle placeholder slots during relocation.
- Stale or drifted derived state.A locked install where mise.lock lacks a tool@version added to mise.toml, a target file modified between plan confirmation and execution, or a cache republishing a different result under the same action digest.
- Upstream schema and format drift.Homebrew cask JSON whose flight-path objects no longer carry a 'base' key, env directive JSON files with null/array/object values that cannot become flat strings, and release assets or archives whose internal layout changed.
- Version floors and version mismatches.A min_version setting in mise.toml hard-blocking older mise binaries at config load, and tool stubs generated by an older mise whose embedded request no longer resolves on the current machine.
- Programmatic misuse of internal APIs.Adding a dependency edge for a resource never inserted into the plan, calling extract_archive on single-file compression formats (Gz/Xz/Bz2/Zst/Br/Lz4/Sz), or calling store() twice with structurally different results under one action digest.
What usually fixes it
- Treat the message as the diagnosis: the interpolated value names the exact offender, so fix that value where it lives — edit the mise.toml entry, rename or symlink the offending path, expand shell variables yourself, or rebalance edit-block markers.
- When bytes are suspect, delete the relevant cache and re-fetch, then inspect what is actually served before retrying (curl the URL, list the tarball with tar -t, jq the JSON) so wrong-byte sources are caught once instead of looped on.
- Verify the network path itself: allowlist tool domains on corporate proxies without body rewriting, and confirm git credentials and reachability interactively (git ls-remote / git fetch) in the same environment the tool runs in.
- Regenerate derived state instead of forcing it: run mise lock after changing tools, re-run planning commands so they replan current content, and clear a stale cache subtree once after a serialization change rather than fighting collisions.
- Change the environment when the environment is the cause: use a glibc-based distro or another backend for brew packages, prefer forward slashes and plain absolute paths on Windows, keep appdir overrides canonical, and shorten deeply nested prefixes.
- Update the tool when upstream schemas drift, and prefer documented escape hatches (registry_floating = false, baked-in registries, digest-pinned images) over suppressing or ignoring the guard.
Go deeper
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Documented occurrences
- archive does not contain registry entries(jdx/mise)
- invalid tool path {s:?}: contains forbidden character {c:?}(jdx/mise)
- invalid tool version {s:?}: contains forbidden character {c:?}(jdx/mise)
- local action key already has a different result(jdx/mise)
- brew bottles are only published for a formula's current version ('{p}'): pin via the formula name instead (e.g. "brew:postgresql@17")(jdx/mise)
- target changed after unapply planning(jdx/mise)
- edits: cannot apply these entries, fix them manually: {}(jdx/mise)
- invalid tool path {s:?}: extended-length and device paths (\\\\?\\, \\\\.\\) are not supported(jdx/mise)
- git -C {} {} failed: {}(jdx/mise)
- unexpected bottle layout for {name}: missing {name}/{pkg_version} in archive(jdx/mise)
- {option}: '{path}' must be a safe relative path (no absolute paths or parent directories)(jdx/mise)
- cannot add dependency to missing bootstrap resource '{resource}'(jdx/mise)
- command output exceeded {max_output_bytes} bytes(jdx/mise)
- brew-cask: invalid appdir '{}'(jdx/mise)
- mise version {min} is required, but you are using {cur}(jdx/mise)
- [dotfiles]."{}/{}": block content may not contain its own marker lines(jdx/mise)
- {err}(jdx/mise)
- extract_archive does not support compressed single-file format: {format}(jdx/mise)
- git failed with status {status}(jdx/mise)
- brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesystem root(jdx/mise)
…and 388 more across the corpus — use search.
Honest provenance: generated on 2026-08-19 from AI-assisted analysis of the linked records. See how records are made.