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.

  1. Permission denied writing to /data
  2. could not store the upload
  3. database has pending migrations, run ‘crane migrate’ first
  4. A path that exists on the host does not exist in the container
  5. refusing to bind to 0.0.0.0 without CRANE_API_TOKEN
  6. address already in use
  7. The interface queues work and nothing ever runs
  8. Every validation fails
  9. the evidence URI is https, which this job cannot read
  10. A validation ran out of time
  11. this database already has data
  12. exit 4, and nothing was written
  13. exit 2
  14. The score looks wrong
  15. Reports say I have no templates
  16. 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 /import or POST /api/v1/import/sbom stores the file under /data/uploads and 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.1 on the host and set CRANE_ALLOW_UNAUTHENTICATED_BIND=1.
  • Bind 127.0.0.1 if 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.


Back to top

CRAne. The database is one SQLite file and it is yours.

This site uses Just the Docs, a documentation theme for Jekyll.