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.

Before 0.20.0 this could also happen to intact files. The skip check needs the remote size, and a HEAD request that failed was silently recorded as “size unknown” — which the check reads as “cannot confirm”, so the file was fetched again. Such kernels show as unknown in the size column. Size queries now retry and reuse connections, so a transient failure no longer costs you a download.

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")"' _ {} \;

get sits on “Querying sizes” for a long time

Before running anything, get asks the archive for each kernel’s size, one HEAD request per entry in KERNELS_TO_LOAD. Large metakernels are therefore slow against a slow server: juice_crema_5_2.tm has 102 kernels, and the ESA archive’s TLS handshake alone was measured at 1.3 s serial and 3.3 s (median) under concurrency.

Connections are kept alive per worker thread since 0.20.0, so the handshake is paid once per thread rather than once per kernel. What remains is one round trip per kernel, which is the archive’s latency rather than anything local. --force skips the size query entirely, at the cost of re-downloading everything.

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 browse → get.

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 — the planetarypy library covers both remaining cases without spice-kernel-db. plp spice fetch <mission> --start <date> --stop <date> builds a metakernel for a date range from the mission’s PDS SPICE archive, and plp spice spk <mission> downloads a live mission’s current trajectory from NAIF’s operational server (planetarypy 0.84 or newer). Automatic support for missions without mk/ is tracked in issue #2.

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/",
]

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