SBOMFlow Error Reference
When a command fails, SBOMFlow prints a stable error code, a one-line fix, and a link back to this page. Codes are stable — once shipped, a code's meaning never changes, so CI logs and bookmarks keep working.
This page complements EXIT_CODES.md (process exit codes for CI) and ONBOARDING.md (first-run walkthrough). If you are brand new, start with:
sbomflow quickstart . # auto-detect, scaffold config, run an offline audit
sbomflow doctor . # report your environment + config factsThe public reference is https://sbomflow.com/docs/errors. The base URL can be overridden with the SBOMFLOW_DOCS_URL environment variable for an internal mirror; the anchors below stay the same. Air-gapped operators can run sbomflow help error <code> for the same catalog without opening a browser (without a code it lists every code and its exit status).
Every code at a glance: did it change anything, and is re-running safe?#
Each code's own section below explains its cause and fix; this table answers the two questions an operator (or a pipeline) asks first. Changed state is what the failing command may have left behind — nothing means the refusal happened before any write, and atomic means artifacts are written via temp-file-plus-rename so a failure leaves either the complete old state or the complete new state, never a torn file. Re-run is whether repeating the same command after fixing the named cause is the intended recovery.
| Code | Exit | Changed state | Re-run after fixing the cause? |
|---|---|---|---|
| E001 / E002 / E003 | 2 | nothing — refused before any write | yes |
| E004 | 2 | atomic — no half-written artifact; temp files cleaned up | yes, once space/permissions are fixed |
| E005 | 2 | nothing — the lock holder owns the directory | yes — wait, use another --output, or --break-stale-lock for a confirmed-stale lock |
| E006 | 2 | nothing — validated before anything is written | yes, with a timezone-explicit timestamp |
| E010 | 2 | nothing | yes, after fixing the named config problem |
| E011 | 2 | nothing — the concurrent decision was preserved, yours was not written | yes — re-running applies your decision on top |
| E012 | 2 | nothing — the request was refused before any network use | yes, with a safe https:// endpoint |
| E013 / E014 | 2 | nothing | yes, with a corrected filter/lineage |
| E015 / E016 / E017 / E018 / E019 | 2 | nothing | yes, with the corrected input/identifier/arguments |
| E021 | 2 | nothing — decisions are append-only; the refused operation recorded nothing | yes, after resolving the named refusal |
| E022 | 2 | nothing — the new decision was refused before any write (fail closed) | yes, after resolving the named prior transaction with decisions recover |
| E031 | 2 | nothing | yes, against a real output directory (or with the named flag) |
| E032 | 2 | atomic — the named artifact was not left half-written | yes, after fixing the named cause |
| E041 | 2 | the integration call failed; local artifacts are unchanged. A partially-applied remote sync is re-run safe: sync is idempotent by stable issue keys | yes |
| E042 | 2 | nothing verified/signed was produced | investigate the key material or artifact first; treat a failed verification as suspect, not as a retry loop |
| E043 | 4 | nothing was appended to the audit log | no — manual intervention: treat the directory as tampered until explained; recover from a known-good copy |
| E051 / E052 | 2 | store/snapshot contents are never modified in place | yes, after the named correction |
| E053 | 2 | nothing — refused before any write; the source store is not mutated and any previous export is intact | yes, once the named member is removed from the release output |
| E099 | 2 | atomic — a crash never leaves a half-written pack | re-run with SBOMFLOW_TRACEBACK=1 and report it: this code is a bug in SBOMFlow, not in your invocation |
E001#
Target directory not found
Exit code: 2
Cause. The path you passed to analyze/audit does not exist.
Fix. Pass an existing product directory (your source or build tree):
sbomflow audit ./my-product --output ./evidence
# or, to get going with sensible defaults:
sbomflow quickstart ./my-productE002#
Target path is not a directory
Exit code: 2
Cause. The path exists but is a file. SBOMFlow scans a directory tree (lockfiles, manifests, SBOMs, firmware images), not a single file.
Fix. Point SBOMFlow at the product's source/build directory. To ingest a single SBOM file, scan its directory and pass the file with --import-sbom:
sbomflow audit ./my-product --import-sbom ./sbom/rootfs.cdx.json --output ./evidenceE003#
Output path is not a directory
Exit code: 2
Cause. The --output path points at an existing file.
Fix. Choose a directory path for --output (it is created if missing):
sbomflow audit ./my-product --output ./evidence-packE004#
Filesystem error reading inputs or writing outputs
Exit code: 2
Cause. The operating system refused a file read or write while producing the evidence artifacts — for example a full disk (ENOSPC), a read-only filesystem (EROFS), or a permission-denied output directory (EACCES). SBOMFlow writes every artifact atomically (temp file + rename), so a failed write leaves the target untouched and cleans up its temp file; it reports this error and exits rather than leaving a half-written pack.
Fix. Make sure the --output directory is writable and the disk has free space, then re-run:
df -h . # check free space
sbomflow analyze ./my-product --output ./evidence-packE005#
Another run holds this directory's lock
Exit code: 2
Cause. A concurrent analyze/audit run owns the transient .sbomflow.lock in the same --output directory (or, for store add / store retain --apply / store empty-trash --apply, in the store root). Two runs writing the same directory would interleave artifact writes and corrupt the evidence pack and the hash-chained audit log, so SBOMFlow refuses with this stable error instead. The message names the holder (pid, host, start time). The lock is advisory and transient: it is never part of any artifact, bundle ZIP, or stored release copy.
Fix. Wait for the other run to finish, or point this run at a different --output directory. If the message says the lock appears STALE — the holding process crashed (its pid is dead on this host) or the lock is hours old on another host — confirm nothing is actually running, then re-run with the explicit flag; a stale lock is never removed automatically:
sbomflow analyze ./my-product --output ./evidence-pack --break-stale-lockOn network filesystems the lock's atomicity depends on the file server (NFSv3 caveats apply) and staleness falls back to age alone — treat it as advisory coordination between cooperating SBOMFlow runs, not a security boundary.
E006#
Invalid timestamp argument
Exit code: 2
Cause. A timestamp flag could not be parsed as an unambiguous point in time — --as-of, or review --digest's --since. The message names which one. Accepted forms: ISO-8601 with an explicit timezone — a trailing Z (2026-07-10T12:00:00Z) or an offset (2026-07-10T14:00:00+02:00) — or a bare date (2026-07-10, read as 00:00:00Z that day). A timestamp without a timezone is rejected as ambiguous, as are hour 24:00, leap seconds, and garbage. Validation happens before anything is written.
This code means a timestamp you typed is unparseable, and nothing else. A command that refuses for an unrelated reason — an unreadable path, a malformed manifest — reports its own family (E015, E016) rather than sending you to check your clock format.
Fix. Add a timezone (usually just append Z):
sbomflow audit ./my-product --output ./evidence --as-of 2026-07-10T12:00:00ZThe canonical form. Whatever accepted form you supply, every artifact records the one canonical UTC form — YYYY-MM-DDTHH:MM:SS+00:00 (microseconds preserved when supplied). …Z, …+00:00, and any offset-equivalent input therefore produce byte-identical artifacts, which is the point: determinism must not depend on timestamp spelling. --since normalizes by the same rule, but it filters which decisions a digest includes rather than being recorded into an artifact.
E010#
Malformed or unknown-key configuration
Exit code: 2
Cause. sbomflow.yaml/sbomflow.json (auto-discovered in the target, or passed via --config) has a syntax error or an unrecognised key.
Fix. Validate it and let the message point at the exact problem:
sbomflow validate-config ./sbomflow.yaml
sbomflow doctor .E011#
Reviewer decision file changed since it was read
Exit code: 2
Cause. Two reviewers triaged the same output directory at once. Between the moment your sbomflow review/sbomflow approve command read the decision file (reviews.json, vulnerability_reviews.json, waivers.json, or approvals.json) and the moment it went to write, another reviewer committed a decision to the same file. Rather than silently overwrite and lose their decision, SBOMFlow refuses and writes nothing.
Fix. Re-run the same command. It re-reads the file (now including the other reviewer's decision) and applies yours on top:
sbomflow review ./out --accept EV-0007 --reviewer you@orgNothing was lost or half-written — the refusal is fail-safe.
E012#
Unsafe network endpoint URL
Exit code: 2
Cause. A user-supplied network endpoint — --osv-base-url, --nvd-base-url, a tracker --base-url, or a notify webhook (SBOMFLOW_WEBHOOK_URL) — was not a safe URL. SBOMFlow refuses to make the request rather than silently speak to an insecure or attacker-controlled endpoint (a URL can arrive via CI-config injection, not just a hand-typed flag).
Fix. Use an https:// URL with no embedded credentials (user:pass@…). Schemes such as file://, ftp://, and data: are rejected. Plain http:// is permitted only for a loopback host (localhost, 127.0.0.1, ::1) for local testing, and even then it warns. Private-range https endpoints (VPC mirrors) are allowed — this is scheme/credential hygiene, not a network filter.
E013#
Invalid review-queue filter expression
Exit code: 2
Cause. sbomflow review --filter … was given an expression SBOMFlow could not parse, a field outside the allowlist, an operator a field does not support, a value of the wrong type, or an unknown @name saved filter. Filter expressions are parsed, never evaluated, so an unrecognised term is refused rather than guessed.
Fix. Write comma-joined FIELD OP VALUE terms (all terms must match). Valid fields and operators:
| Field | Operators | Value |
|---|---|---|
severity | >= > <= < = != | none/low/medium/high/critical/unknown |
epss | >= > <= < = != | a number, e.g. 0.5 |
kev | = != | true / false |
status / ssvc / kind | = != | text |
assigned | = != | a reviewer identity, or me (needs --reviewer) |
component | = != | substring match |
Example: --filter 'severity>=high,kev=true'. Save reusable views in sbomflow.yaml under review.filters.<name> and select one with --filter @name. A missing measurement (e.g. no EPSS score) satisfies no comparison — missing is never treated as zero.
E014#
Invalid declared release lineage
Exit code: 2
Cause. A product's declared release lineage is impossible. Either a release names itself in release.parent_release_id, or the declared parents form a cycle (A → B → A). SBOMFlow refuses the lineage rather than rendering it: a wrong parent produces a misleading drift story, which is worse than no lineage at all.
Fix. Edit release.parent_release_id in sbomflow.yaml so it names a different, earlier release of the same product.
release:
id: "gateway-2.4.1"
parent_release_id: "gateway-2.4.0" # not "gateway-2.4.1"Not an error. A parent id that SBOMFlow cannot resolve — for example an early release that predates your local evidence store — is not refused. It is recorded with parent_status: unverified and the release is still shown as a lineage root, because SBOMFlow never guesses a parent from version sorting.
E015#
Input file not found or unreadable
Exit code: 2
Cause. A file path you passed (a snapshot, reviews file, report, manifest, policy…) does not exist or cannot be read. Relative paths resolve from the directory you ran the command in.
A directory a command must read as a whole lands here too: migrate-in when its source estate is not a readable directory, and next-actions when its output directory is missing or holds no readable evidence-pack.json (run sbomflow analyze first — the message says so).
Fix. Check the path and its permissions; pass an existing, readable file.
E016#
Malformed input document
Exit code: 2
Cause. The file exists but could not be parsed as the format the command expects — truncated/invalid JSON, a document of the wrong shape, or a field that fails validation. The message names the precise problem. SBOMFlow never guesses around malformed input (it would manufacture evidence).
A batch-run manifest that cannot be read as a set of runnable jobs reports here — unparseable, the wrong shape, a job missing source or output, or two jobs sharing a name or an output directory. A manifest path that does not exist at all is E015 instead.
Fix. Regenerate the file with its producing tool, or fix the named field.
E017#
Unknown identifier for this release
Exit code: 2
Cause. The vulnerability id, evidence key, gap id, or other identifier you named is not present in the output directory the command read. Identifiers are case-sensitive and release-specific.
Fix. List the current items first (for review flows: sbomflow review <dir>) and copy the exact identifier from the queue.
E018#
No recognized inputs found under the path
Exit code: 2
Cause. An importer (ESP-IDF, Zephyr/west, Twister, linker-map, OCI…) scanned the directory and found none of the files it recognizes. The message says what it expected to find.
Fix. Point the importer at the tool's real output root, or run the build step that produces those files first.
E019#
Invalid argument value or flag combination
Exit code: 2
Cause. An option value is out of range or malformed, or two flags cannot be used together. The message names the offending value or combination.
Fix. Run the command with -h for the accepted forms and ranges.
E021#
Review, approval, or merge operation refused
Exit code: 2
Cause. A reviewer-workflow command was refused: recording over an immutable prior decision, an invalid not_affected justification, a conflicting decision merge, a missing approval role, an out-of-scope revoke… The message states exactly what was refused and why.
Fix. Decisions are append-only and nothing was partially recorded — fix the named input (or resolve the named conflict) and re-run.
E022#
New decision refused: an unsafe prior transaction must be resolved first
Exit code: 2
Cause. Every consequential decision write first heals or blocks on any crash-recovery transaction still open on the same target (see recovery playbooks PB-01/PB-02). When a relevant prior transaction is in a state SBOMFlow will not silently write over — recoverable, corrupt, unsupported, state_mismatch, or audit_mismatch (the registered verify-transactions statuses) — the new decision is refused fail-closed rather than layered on top of an unresolved one. This is distinct from E021: E021 is a domain refusal of this operation (immutable prior decision, invalid justification, missing role…); E022 is specifically "an earlier transaction on this target is not in a safe state yet".
Fix. Nothing was written. Diagnose and resolve the named prior transaction, then re-run the same command (its automatic pre-write recovery then finds the store clean):
sbomflow decisions verify-transactions --store ./evidence
sbomflow decisions recover --store ./evidence --operation-id <operation-id>A corrupt prior journal is a documented manual-quarantine path (PB-02); a recoverable one is completed or aborted by recover (PB-01).
E031#
Not a usable SBOMFlow output directory for this command
Exit code: 2
Cause. The directory you pointed the command at is not a (complete) sbomflow analyze/audit output — a required artifact is missing — or it holds a previous run that this command refuses to silently overwrite or merge.
Fix. Pass a directory produced by analyze/audit. When replacing or merging an existing output, the message names the exact flag to add.
E032#
Failed to render or write an artifact
Exit code: 2
Cause. A named artifact (dashboard, reviewer console, portfolio, auditor package, bundle zip, PDF…) could not be produced. The message carries the underlying reason.
Fix. Outputs are written atomically, so nothing was left half-written. Fix the named cause (often disk space or permissions — see E004) and re-run.
E041#
Tracker sync or notification failed
Exit code: 2
Cause. An opt-in integration call — Jira, ServiceNow, GitHub issues, Dependency-Track, or a notify channel — failed. The message carries the service's response. These paths run only with explicit --apply.
Fix. Check the credential environment variables the integration documents, the endpoint URL (https required, see E012), and connectivity. A dry-run without --apply plans offline and needs none of them.
E042#
Signing or integrity verification failed
Exit code: 2
Cause. A signature could not be created or verified, or a file's bytes did not match their recorded digest. The message names the artifact and reason.
Fix. Verify you are using the intended key material and an untampered input. For verification failures, treat the artifact as suspect until you can re-obtain it from its source.
E043#
Audit log unreadable or hash chain broken
Exit code: 4
Cause. A reviewer/approval/export command refused to touch the append-only audit log: the file could not be read, or its hash chain no longer verifies — the tamper-evidence signal the log exists to give. Nothing was recorded onto it.
Fix. Treat the output directory as tampered until explained. Recover the release from a known-good copy (for stored releases, sbomflow store verify and the store's canonical copy) instead of editing the log.
E051#
Evidence-store operation failed
Exit code: 2
Cause. The local evidence store refused the operation: the store directory is missing or unwritable, a release id is unknown, or stored bytes failed an integrity check. A store export that is refused for one of those reasons — rather than by the share-boundary preflight, which is E053 — also lands here.
Fix. sbomflow store list and sbomflow store verify show the store's current state; the message names what to correct.
E052#
Vulnerability-database operation failed
Exit code: 2
Cause. A local advisory snapshot or the vulndb index could not be read, verified, or updated. The message names the snapshot and problem.
It also covers vulndb verify finding nothing to verify — no store at --dir, a store holding zero snapshots, or snapshot manifests the derived index does not list. Verifying nothing is not a pass: zero snapshots checked would otherwise render as all snapshots verified with exit 0, which is the most reassuring possible answer from the one command whose job is reporting whether your advisory data is intact. "We could not answer" is a different fact from "we answered and found nothing wrong". Exit 1 remains reserved for the second case — snapshots were checked and at least one failed its hash or record count.
It also covers vulndb retention --apply being handed a plan that proposes deletions without recording that the release store behind it was read. Retention deletes snapshot directories, so the function that calls the deletion refuses a plan whose basis is not on the plan itself, rather than trusting that its caller computed the removal set from evidence.
Fix. sbomflow vulndb inspect shows the pinned snapshot state. Re-pin or re-download a corrupt snapshot; snapshots are inputs and are never modified in place. When there was nothing to verify, the message names the remedy: sbomflow vulndb update to record a snapshot, sbomflow vulndb reindex to rebuild an index that lost track of manifests still on disk, or a corrected --dir. For a refused retention plan, re-run sbomflow vulndb retention with --release-store pointing at the store that actually holds your releases.
E053#
Export refused: the estate carries material that must not be shared
Exit code: 2
Cause. A portable export is an explicit share boundary, so store export runs a preflight over the store's own members before writing anything and refuses rather than publishing when a member carries private-key material, credential/token material, a secret-shaped filename, an absolute machine path, or a violation of the same sharing contract the reviewer bundle uses. The message names the affected members and their violation classes — never the values. Nothing was exported, and any previous export at that destination is untouched.
Analyzing with an absolute target path records that path in the pack as provenance, and the preflight refuses to publish it at the share boundary. If an estate is intended to travel, run analyze from inside your workspace with a relative target.
Fix. Remove the material from the release output and re-run store add, or move it outside the store, then export again. SBOMFlow will not filter or redact the member for you: silently dropping a record would break the completeness guarantee the export exists to provide, and rewriting stored bytes would invalidate their recorded hashes (and, for the hash-chained audit log, destroy its evidence properties).
A clean preflight means those classes were not observed in the members that could be inspected. Members that could not be inspected are listed and counted separately — "not inspected" is not "clean" — and a clean result is never a claim of legal safety or of freedom from every possible secret.
E099#
Unexpected internal error
Exit code: 2
Cause. SBOMFlow hit an exception no handler anticipated. This is a bug in SBOMFlow, not in your invocation. Rather than print a Python traceback, the CLI reports this code and exits 2 — never 1, because 1 means an enforced gate blocked the release, and a pipeline must be able to tell "policy said no" apart from "the tool could not run".
Fix. Re-run with the traceback enabled and include it in a bug report, together with the command and your version:
SBOMFLOW_TRACEBACK=1 sbomflow validate ./evidence
sbomflow --versionEvidence artifacts are written atomically (temp file + rename), so a crash never leaves a half-written pack behind.
My error isn't listed here#
Most other failures are warnings (SBOMFlow warns, never silently ignores) and appear in scan-warnings.json with a stable warning key — see the warning code reference. For process exit codes in CI, see EXIT_CODES.md. If a failure is unclear, run sbomflow doctor . and open an issue with the output.