Troubleshooting
Every entry here is a real message you can hit, what causes it, and what to do. Most are CRAne telling you something true about your environment rather than failing.
- Permission denied writing to /data
- could not store the upload
- database has pending migrations, run ‘crane migrate’ first
- A path that exists on the host does not exist in the container
- refusing to bind to 0.0.0.0 without CRANE_API_TOKEN
- address already in use
- The interface queues work and nothing ever runs
- Every validation fails
- the evidence URI is https, which this job cannot read
- A validation ran out of time
- this database already has data
- exit 4, and nothing was written
- exit 2
- The score looks wrong
- Reports say I have no templates
- Still stuck
Permission denied writing to /data
sqlite3.OperationalError: unable to open database file
The container runs as uid 10001, and a bind mount keeps the host’s ownership. Two causes,
usually together:
The directory did not exist. Docker created it as root when it mounted it. Create it yourself first:
mkdir -p data exports runs
The container’s user is not you. Hand it your uid:
docker run --user "$(id -u):$(id -g)" ... # plain docker
export CRANE_UID=$(id -u) CRANE_GID=$(id -g) # compose
If the directory is already owned by root from an earlier attempt:
sudo chown -R "$(id -u):$(id -g)" data exports runs
On Fedora, RHEL and derivatives add :z to the mount, or SELinux blocks it regardless of
ownership: -v "$PWD/data:/data:z".
could not store the upload
could not store the upload: Permission denied on '/data/uploads'
The sneakier sibling of the problem above: the database opens fine, pages render, and
then the first upload through the wizard or POST /api/v1/import/sbom is refused. The
server could read everything and write nothing new — usually a /data volume restored
from a backup with different ownership, or an uploads/ directory created as root by an
earlier attempt.
The refusal names the directory and the uid the server runs as. Fix the ownership as
shown above, or point CRANE_UPLOADS at a directory the server’s uid can write. Nothing
was registered for the refused file: fixing the volume and uploading again is the whole
recovery.
database has pending migrations, run ‘crane migrate’ first
The image is newer than the database. Expected after an upgrade:
crane migrate --dry-run # what would run
crane migrate
Back up the file first. There are no down migrations, and the way back is the copy you made.
A path that exists on the host does not exist in the container
input not found: batch.json
or, from a validation job:
the SBOM file does not exist at /home/me/sboms/gateway.spdx.json
This is the single most common mistake with the container, and it costs an afternoon if you meet it cold. CRAne resolves every path on its own filesystem. A path that is real on your host is not real inside the container unless you mounted it there.
For input files: write them under a directory you mounted, and pass the container path.
cp batch.json data/ # data/ is mounted at /data
crane import data /data/batch.json --json
For registered evidence, the worker resolves the URI you stored. Either mount the documents
and register container paths, or mount the host path at the same location inside the
container so both agree. crane job show <id> prints the path it tried, which is the
fastest way to tell which of the two you have.
Uploading through
/importorPOST /api/v1/import/sbomstores the file under/data/uploadsand registers it for you, which sidesteps the problem entirely.
refusing to bind to 0.0.0.0 without CRANE_API_TOKEN
Working as intended. Inside a container the server must bind 0.0.0.0 for a published port
to reach it, and it cannot see how that port was published. Pick one:
- Set
CRANE_API_TOKEN. Correct almost always. - Map the port to
127.0.0.1on the host and setCRANE_ALLOW_UNAUTHENTICATED_BIND=1. - Bind
127.0.0.1if you are not in a container.
Full detail under Exposing it beyond this machine.
address already in use
Something already holds the port — often a CRAne you forgot was running.
lsof -nP -iTCP:8000 -sTCP:LISTEN # macOS, Linux
docker ps --filter publish=8000
Stop it, or publish a different host port: -p 127.0.0.1:8001:8000.
The interface queues work and nothing ever runs
crane serve without --worker serves the interface and executes nothing. Uploads
register, validations queue, and the queue grows.
crane serve --host 0.0.0.0 --port 8000 --worker
Or run a worker separately — docker compose --profile worker up -d, or
crane worker run --once on a schedule. Running both is safe; a claimed job is leased.
Every validation fails
claimed 15, succeeded 0, failed 15, retrying 0
Exit code 5. Ask why:
crane job list --status failed --json
“executable not found” — the plain image carries no validator. Use the -ossbomer
image, or install ossbomer next to the worker. Nothing is scored as
failing because of this; the check is recorded as insufficient evidence, which is a
coverage gap and not a verdict.
“the SBOM file does not exist” — a path problem, above.
A profile error — the profile a policy names may have been withdrawn upstream, which is
worse than missing because it looks transient. Run crane validate profiles and read
The validator.
the evidence URI is https, which this job cannot read
The document is registered at a URL and the validation job will not fetch it. Fetch it yourself, under your own network policy, and the job then reads the local copy by its hash:
crane manage evidence-fetch --dry-run
crane manage evidence-fetch --by "your name"
crane validate sbom
A fetch that fails leaves the document inaccessible with the reason on its change
record. Fix the URL or the network and run it again; a document in that state is retried.
A validation ran out of time
The job ended timed_out, and its error says how large the document was and the budget it
had. The budget is the job definition’s limit (600 seconds) plus
CRANE_OSSBOMER_SECONDS_PER_MB (default 60) for each megabyte, so a document that runs out
is either very large or on a slow machine. Retrying from the job’s page runs the same
budget again and ends the same way.
Raise the rate where the worker runs, then queue the document again:
export CRANE_OSSBOMER_SECONDS_PER_MB=180
crane validate sbom # queues the timed-out document again, with anything not yet queued
crane worker run --once
The SBOM page shows these under “Ran out of time”, apart from documents that are merely still queued.
this database already has data
this database already has data. Seeding is only for an empty one so it cannot be
mistaken for real inventory. Use --force if you are sure.
crane demo deliberately refuses to load fiction on top of anything real. To start over,
delete the file:
rm -rf data/crane.db data/demo-sboms
crane init
exit 4, and nothing was written
The batch did not validate. "written": {} confirms nothing was applied — validation runs
over the whole batch before anything is written, so there is no half-loaded state.
{"dataset": "products", "row": 1, "column": "nonsense_column",
"message": "unknown column. Unknown columns are rejected so a typo is never silent data loss"}
Fix the rows and retry. Retrying unchanged input fails identically.
exit 2
The command is specified but not built yet, or the adapter you named is not installed.
crane import sbom is the current example: upload through the interface or
POST /api/v1/import/sbom instead.
For adapters, published images carry none by design. crane import adapter with no name
lists what this deployment actually has.
Do not work around a 2. It means the thing you asked for does not exist yet.
The score looks wrong
Read the denominator before the score. Products with no scope decision are counted neither as passing nor as failing, so a portfolio where nobody has decided scope will report a small denominator and a score that means very little.
crane assess cra --detail # per check, with the reasoning
crane assess cra --subject <slug> # one subject
Check which lens you are reading. cra.regulatory is the reporting default, and a product
in scope under commercial may be out of scope under it. Both statements are kept on
purpose.
Reports say I have no templates
Templates are your content and are deliberately not baked into the image. Mount the directory:
mkdir -p report-templates
docker run -v "$PWD/report-templates:/report-templates:ro" \
-e CRANE_REPORT_TEMPLATES=/report-templates ...
The published compose file does this already.
Still stuck
crane status first — it prints the database location, what is applied, what is pending
and the row counts, which resolves most “is it even looking at the right database”
questions. Then crane job show <id> for anything that ran, which prints the parameters,
the events, the artifacts and the run directory holding stdout and stderr.