Troubleshooting

Common issues and solutions

“Database is locked”

DuckDB uses file-level locking. Only one process can write at a time.

Symptoms: duckdb.IOException: Could not set lock on file

Solutions:

  • Wait for the other process to finish (e.g., a running get or scan).
  • Read-only commands (check, list, resolve, stats, mk, coverage, duplicates) open the database in read-only mode and can run concurrently with a writer.
  • If no other process is running, the lock file may be stale. Delete <db_path>.wal (the write-ahead log) and retry.

“No locally acquired metakernels”

You need to configure a mission and download a metakernel first.

spice-kernel-db mission add     # set up a mission
spice-kernel-db browse MISSION  # see what's available
spice-kernel-db get             # download one interactively

“Not found” when resolving a kernel

The kernel exists on disk but isn’t in the database yet.

# Re-index the directory containing the kernel
spice-kernel-db scan /path/to/kernels

spice-kernel-db get downloads everything again

By default, the tool skips files that match both the expected size and SHA-256 hash. If files were corrupted or partially downloaded, they’ll be re-downloaded. Use --force to bypass all skip checks.

Hash mismatch for <name>: computed X, expected Y during get/update

This was a download bug, fixed by writing each download to a temporary file and atomically replacing the destination (rather than open(dest, "wb"), which followed symlinks).

The cause: content-addressed deduplication leaves a symlink at an alias filename pointing to the shared canonical kernel. When the alias and its target later diverge upstream (e.g. a mission’s predict CK and a measured CK that were once byte-identical), re-downloading wrote the new bytes straight through the symlink into the shared file. Parallel downloads of several aliases pointing at one file would clobber each other, and the corruption surfaced as this hash-mismatch error (the integrity check correctly refusing the bad file).

If you hit this on an older version, recover by removing the affected files and re-fetching:

# Re-fetch the metakernels that touch the corrupted kernels
spice-kernel-db get <metakernel-url> --force
# Then re-run dedup so identical content is re-linked cleanly
spice-kernel-db dedup

To inspect which alias filenames share a physical file, look for cross-name symlinks in the kernel tree:

find <kernel-dir> -type l -exec sh -c \
  '[ "$(basename "$1")" != "$(basename "$(readlink "$1")")" ] && echo "$1 -> $(readlink "$1")"' _ {} \;

Metakernel paths don’t work with SpiceyPy

SPICE resolves PATH_VALUES relative to the current working directory, not the metakernel file location. The tool writes absolute PATH_VALUES in saved .tm files so they work from any directory.

If you see SPICE(NOSUCHFILE), check that the paths in the .tm file are absolute:

grep PATH_VALUES /path/to/your.tm

SpiceyPy not found for coverage

The coverage command requires the optional spiceypy dependency:

pip install spice-kernel-db[spice]

Or install SpiceyPy separately: pip install spiceypy

Corrupt config file

If config.toml is malformed, the tool prints an error and falls back to interactive setup. To fix manually:

# Show the config file location
spice-kernel-db config

# Re-run setup
spice-kernel-db config --setup

Or edit ~/.config/spice-kernel-db/config.toml directly:

[database]
path = "~/.local/share/spice-kernel-db/kernels.duckdb"

[storage]
kernel_dir = "~/.local/share/spice-kernel-db/kernels"

Stale entries after deleting files

If you’ve deleted kernel files from disk, the database still references them. Use prune to clean up:

# Preview what would be removed
spice-kernel-db prune

# Actually remove stale entries
spice-kernel-db prune --execute

update fails with “Metakernel unreachable” / HTTP 404

NAIF (and ESA) routinely rotate older versioned metakernel snapshots into a former_versions/ subdirectory. Once that happens, the original URL stored in your local metakernel_registry returns 404, and spice-kernel-db update <mk> aborts with a red panel like:

╭─── Metakernel unreachable ───╮
│ The remote metakernel is no longer available.
│   URL:      …/mk/juice_crema_5_1_…_v462_20260223_001.tm
│   Status:   HTTP 404
│ NAIF (and ESA) routinely rotate old versioned snapshots into a
│ former_versions/ subdirectory, which makes the original URL 404.
╰──────────────────────────────╯

The exit code is 2 (distinct from generic LookupError’s 1), so scripts can detect this case specifically.

Fix it with prune --metakernels:

# Preview every dead registry row (HEAD-probes each source_url)
spice-kernel-db prune --metakernels

# Actually remove the dead rows
spice-kernel-db prune --metakernels --execute

# Also delete the on-disk .tm files (symlink trees are shared across
# metakernels in a mission and are never auto-deleted)
spice-kernel-db prune --metakernels --execute --delete-files

The pruner treats 403, 404, and 410 as permanently dead; transient errors (timeouts, DNS failures, 5xx) deliberately don’t classify a row as dead, so going offline never causes accidental deletion.

If you just want to remove the one offending entry:

spice-kernel-db mk --remove juice_crema_5_1_…_v462_20260223_001.tm

Then re-acquire the current version with browseget.

Want to re-fetch the archived version instead? get automatically retries under former_versions/ when the live mk/ URL 404s, so:

spice-kernel-db get juice_crema_5_1_…_v462_20260223_001.tm --mission JUICE

…will transparently download the archived metakernel and its referenced kernels (which ESA keeps live in kernels/{spk,fk,…}/ even after a .tm is rotated out). This is the recommended way to reproduce analyses against a specific older metakernel version.

verify reports unexpected statuses

After a get or rewrite, run spice-kernel-db verify <mk> to cross-check the rewritten metakernel against the DB. Common findings:

Status What it means What to do
DANGLING Symlink target moved or was deleted Re-run get/update on this metakernel — the fixed _link_existing_kernels now repairs dangling links.
HASH_MISMATCH (deep mode) File was modified after registration prune --execute to drop the location, then re-get to fetch a clean copy.
AMBIGUOUS Two non-superseded kernels rows share this filename Indicates the DB has two distinct files with the same name. Use find_by_filename in the Python API to see them, decide which to keep, and prune the other.
UNREGISTERED File on disk but no kernels row Run scan <dir> on the directory containing it.
BAD_PATH_VALUE PATH_VALUES not absolute Use get (which writes absolute paths) instead of editing .tm files by hand.
TRAVERSAL A KERNELS_TO_LOAD entry escapes PATH_VALUES The metakernel is malformed or hostile — don’t furnsh it.

Mission has no metakernel directory at the default path

mission add looks for a metakernel directory at {server_url}{MISSION}/kernels/mk/. Most NASA missions on NAIF do not have one — they publish individual per-type kernels (ck/, fk/, lsk/, pck/, sclk/, spk/) but no curated mission-wide .tm file. An empirical survey of NAIF in 2026 found:

  • 8 missions with kernels/mk/ (mostly ESA-mirror entries — BEPICOLOMBO, EXOMARS2016, JUICE, LADEE, MEX, ROSETTA, SMART1, VEX)
  • 68 missions with no mk/ subdirectory anywhere on NAIF
  • 0 missions using alternative templates like spice_kernels/mk/, data/spice/mk/, etc.

When the default path 404s, you have two productive options:

  1. Manual override — pass --mk-dir-url <URL> to mission add. The URL is HEAD-checked but otherwise used as-is. Use this when you’ve found the metakernel directory yourself — typically on a PDS node such as pds-imaging.jpl.nasa.gov or pds-smallbodies.astro.umd.edu.
  2. planetarypy — for PDS-archived missions, the planetarypy library is the right tool. Install the optional bridge with pip install spice-kernel-db[planetarypy] and pass --use-planetarypy on mission add. Full delegation is tracked in the issue tracker — the current stub prints a notice and falls back to normal discovery.

The curated mission_registry.toml exists as an extension point: if you confirm a working metakernel directory (anywhere on the web), please open a PR adding an entry so other users benefit:

["YOUR-MISSION"]
candidates = [
  "{server}{m}/some/alt/path/mk/",
  "https://some-pds-node.example/spice/your-mission/mk/",
]
planetarypy = false   # set to true if planetarypy manages this mission

Registry entries can use absolute URLs (PDS nodes, mission websites) — the {server}/{m} placeholders are convenient but not required.