Skip to main content

Exit codes

Tome exits 0 on success. Every failure class maps to its own specific non-zero code — there is no generic "unknown error" arm — so you can branch on the exact failure in scripts. The --json error output also includes a snake-case category for each failure (mostly one per code, though a couple of codes share a category — e.g. 52 and 73 both report schema_too_new). This table is enforced against the CLI contract in CI.

The --json error envelope

A failed command with --json prints one error record to stderr (never stdout):

{
"error": {
"category": "index_busy",
"exit_code": 50,
"message": "another tome process is updating the index; retry once it has finished\nhint: the advisory lock is held by a live process and self-heals when that process exits — there is no lock file to delete; retry shortly",
"retryable": true
}
}

The fields are:

  • category — the snake-case failure class (the middle column of the table below).
  • exit_code — the integer this command exited with.
  • message — the human-readable message (already credential-scrubbed).
  • retryable — a boolean, always present. true for transient or contended failures where retrying the same command unchanged could succeed (index_busy, harness_clash, a network/remote provider call, a git fetch); false for deterministic failures (a malformed manifest, an unknown catalog, a strict-mode verdict).
  • remediation — the coarse tome command that fixes this class of failure, when a single one exists (e.g. embedder drift → tome reindex --force, plugin_not_convertedtome plugin convert). Omitted entirely when there is no single fix command. It is always a static tome … command hint — it never contains a path, credential, or other instance-specific value (those stay in message).

When a failure carries both retryable: true and a remediation (today only harness_clash), the two are sequenced: apply the remediation first, then retry. Re-running the identical command without applying the fix just reproduces the same error.

Branch on retryable / remediation rather than string-matching message:

tome query "…" --json 2>err.json || {
jq -e '.error.retryable' err.json >/dev/null && echo "will retry"
fix=$(jq -r '.error.remediation // empty' err.json); [ -n "$fix" ] && echo "run: $fix"
}

The same category / retryable / remediation triple is attached to the MCP tool error data payload (alongside its code), so an agent driving Tome over MCP branches on the identical structured data.

Code 10 is special: it is not a failure class but a health verdict emitted by tome status and tome doctor (see Health verdicts below). It is never carried by the --json error envelope's category.

CodeCategoryMeaning
0Success.
1internalInternal error.
2usageInvalid usage / arguments.
3catalog_not_foundCatalog not found.
4catalog_already_existsCatalog already exists.
5manifest_invalidCatalog manifest (tome-catalog.toml) invalid.
6git_failedA git operation failed.
7ioI/O error.
8interruptedInterrupted (SIGINT / Ctrl-C).
9plugin_data_dir_write_failedFailed to write a plugin's data directory.
10health_degradedtome status / tome doctor health verdict: degraded (a non-fatal issue — queries still serve). See Health verdicts.
12workspace_not_boundNo workspace is bound to the current directory (tome workspace current).
13workspace_not_foundWorkspace not found.
14workspace_already_existsWorkspace already exists.
15workspace_name_invalidInvalid workspace name.
16workspace_has_bound_projectsWorkspace still has bound projects.
17composition_errorWorkspace composition error.
18harness_not_supportedUnsupported harness.
19harness_clashHarness configuration clash.
20plugin_not_foundPlugin not found.
21plugin_already_in_statePlugin already in the requested state.
22plugin_manifest_parse_errorPlugin manifest (tome-plugin.toml) parse error.
23skill_frontmatter_parse_errorSKILL.md frontmatter parse error.
24summariser_failureSummariser failure.
25workspace_data_dir_write_failedFailed to write a workspace's data directory.
26prompt_argument_mismatchMCP prompt argument mismatch.
27entry_not_foundEntry not found.
28substitution_failedVariable substitution failed.
29invalid_argument_frontmatterInvalid argument frontmatter.
30model_missingA required model is missing.
31model_corruptA model file is corrupt.
32model_checksum_mismatchModel checksum mismatch.
33model_registration_parse_errorModel registration parse error.
34inference_runtime_init_failureInference runtime failed to initialise.
35vector_extension_init_failureVector extension failed to initialise.
36embedding_generation_failureEmbedding generation failed.
37reranking_failureReranking failed.
40query_no_results_strict--strict query returned no results.
41embedder_name_driftEmbedder name drift (index vs. configured model).
42embedder_version_driftEmbedder version drift.
43hook_spec_parse_errorHook spec parse error.
44hook_settings_write_failedFailed to write hook settings.
45agent_translation_failedAgent translation failed.
46guardrails_write_failedFailed to write the guardrails file.
47reindex_scoped_embedder_changeA scoped reindex was refused because the embedder changed — run a full tome reindex.
50index_busyThe index is locked by another process.
51index_integrity_check_failureIndex integrity check failed.
52schema_too_newIndex schema is newer than this binary supports.
53catalog_has_enabled_pluginsCatalog still has enabled plugins (use --force).
54not_a_terminalAn interactive command was run without a terminal.
60mcp_startupMCP server failed to start.
61mcp_ioMCP protocol I/O error.
70workspace_malformedWorkspace data on disk is malformed.
73schema_too_newWorkspace schema version too new.
74schema_migrationSchema migration failed.
75doctor_fix_unsafeA doctor --fix repair was not safe to apply.
80plugin_not_convertedPlugin not converted: legacy .claude-plugin/plugin.json exists but no tome-plugin.toml.
81output_existsRefusing to overwrite existing output (pass --force).
82template_invalidTemplate unusable (missing file, malformed template, render error).
83source_format_unrecognizedCould not auto-detect source format (pass --from <harness>).
84conversion_unsupported_strictconvert --strict hit an unsupported feature.
85validation_found_errorslint found at least one error.
86validation_strict_warningslint --strict found warnings (and no errors).
87meta_skill_not_foundUnknown bundled meta skill id.
88meta_install_failedFailed to install a meta skill.
89no_harness_detectedNo supported harness detected (use --harness or install one).
90telemetry_endpoint_unreachableTelemetry endpoint unreachable (vestigial — retained for the closed-set contract; not constructed today).
91telemetry_config_invalidTelemetry config invalid (vestigial — retained for the closed-set contract; not constructed today).
92telemetry_queue_corruptTelemetry queue corrupt: unparsable lines were dropped (tome telemetry inspect).
93provider_config_invalidProvider config invalid: an undefined provider reference, a kind illegal for the capability, a provider set without a model, or a configured external provider with no resolvable credential (tome models test).
94provider_request_failedA remote provider request failed (auth, rate-limit, timeout, unreachable, malformed response).
95remote_embedding_invalidA remote embedding failed content validation (empty / non-finite / wrong dimension).

Authoring verdicts

Codes 85 and 86 are verdicts, not crashes: lint ran to completion and is reporting what it found — 85 means at least one error, 86 means warnings-only under --strict. Scripts and CI should branch on them (a 0 means no findings; anything else in this pair is feedback, not a tool failure). See Linting.

Health verdicts (status / doctor)

tome status and tome doctor render a report and then exit with one of three health verdicts — the report always prints first, so these codes never suppress the diagnosis:

VerdictCodeMeaning
Healthy0Everything checks out.
Degraded10A non-fatal issue (e.g. the reranker or summariser is missing, or a catalog cache is broken) — queries still serve.
Unhealthy1A fatal issue (broken index, embedder drift, malformed config).

Both non-zero verdicts fail a plain "fail on any non-zero" gate. The distinct 10 lets a stricter gate fail on unhealthy only:

tome status; code=$?
if [ "$code" -eq 1 ]; then
echo "unhealthy — failing the build"; exit 1
elif [ "$code" -eq 10 ]; then
echo "degraded — warning only"; exit 0
fi

Equivalently, gate on the structured field — status --json | jq -r .overall and doctor --json | jq -r .overall both yield ok / degraded / unhealthy, which is the recommended, code-independent gating source.

For tome doctor --fix: when the repair runs but un-fixable issues remain, it exits 75 (doctor_fix_unsafe) instead of the health verdict — "the fix did something, but manual work is still required".