Developer guide
Crux Daemon · 12. Troubleshooting
Crux Daemon developer guide
12. Troubleshooting
Every message below is quoted from the code that emits it. Search this page for the string you actually saw.
The general daemon error catalogue is docs/error-catalogue.md; this page covers the extension and integration surface.
12.1 Nothing happens — no error at all
The most common failure mode, because most of the surface fails silent by design.
| Symptom | Cause | Fix |
|---|---|---|
A tool is absent from MCP tools/list | No grant for the calling passport, or the tool is not in allowed_tool_names | Issue or widen the grant (§3.4) |
| A tool is absent even with a grant | The caller has no passport-bound context — list_extension_tools returns empty (extensions.rs:40) | Bind a passport, or send an RCX token |
| A tool is absent, grant and passport both fine | The RCX capability token lacks crux-extension.<tool_name> (tools/mod.rs:2499) | Mint a token carrying that capability |
tools/call says unknown tool although tools/list shows it | The name does not start with ext., so dispatch never routes to it (extensions.rs:179) | Rename the tool; there is no other fix |
| Vault watcher never runs | Half the double gate is missing | Check the boot log for vault-watcher inactive (§7.2) |
A file_watcher pack is installed but nothing runs | Installed is not granted — enabled_packs_of_kind only sees enabled grants | POST the grant route |
No __sync__::<job> fact exists | The job returned Skipped — no fact is written (sync_scheduler.rs:176) | Expected when unconfigured |
| A first status fact does not appear | The first run is one full interval after boot, never at boot (sync_scheduler.rs:258) | Wait one interval |
| Fact writes silently vanish | Entity outside allowed_prefixes_write | Read dropped_fact_writes and drop_reasons in the response |
12.2 Validation rejections (400)
From IntegrationError (lib.rs:58):
| Message | Meaning |
|---|---|
invalid integration schema '<x>' | schema is not crux.integration.v1 |
field '<f>' is required | Empty after trim. Fields: id, name, version, publisher_passport_fpr, entry.path, external_tool_endpoint, tools, tools[].name, tools[].description, wasm_module_sha256, wasm_module_path or wasm_module_url |
invalid identifier '<id>' | id contains a character outside [A-Za-z0-9._-] |
unknown capability '<c>' | Not in the 14-item allowlist (§3.1) |
external helpers are disabled by policy | entry.kind: external_helper without CORECRUXD_INTEGRATIONS_ALLOW_EXECUTABLE_HELPERS |
manifest hash mismatch: expected <a>, actual <b> | hashes.manifest does not match the recomputed value. Usually: edited after signing |
signature is required | Unsigned, and no bypass applies |
unsupported signature algorithm '<alg>' | Only ed25519 |
no trusted public key for passport '<fpr>' | The signing fingerprint is not in trusted-keys.json |
invalid signature material: signature public_key_hex does not match trusted keyring entry | The manifest's inline key differs from the keyring's |
invalid signature material: signature must be 64 bytes | Bad base64 length in sig |
signature verification failed | The signature does not verify. Usually: payload edited after signing |
pack '<id>' version '<v>' is not installed | Granting before installing |
grant for pack '<id>' and passport '<fpr>' was not found | Disabling a grant that does not exist |
capability '<c>' is not declared by pack '<id>' | Granting more than the manifest asks for |
invalid path component '<x>' | A pack id, version or fingerprint containing something outside [A-Za-z0-9._-] |
invalid index schema '<x>' | <data_dir>/integrations/index.json has the wrong schema tag |
Kind-specific ones:
| Message | Fix |
|---|---|
external_tool_endpoint must be an http(s) URL, got '<u>' | Use a full URL with a scheme |
wasm_module_* fields only valid when entry.kind=wasm | Remove them from a non-wasm manifest |
external_tool_endpoint, tools[], or wasm_module_* fields are only valid when entry.kind in {external_tool, wasm}, got <Kind> | Same, for the other kinds |
external_tool_endpoint must be unset for entry.kind=wasm | Remove it |
wasm tools must not set tools[].auth_shared_secret_id; use crux::get_secret_decrypted in-module instead | Remove it — and note the host function is a stub (§6.5) |
wasm_module_sha256 must be 64 lowercase hex chars, got N chars | Lowercase, no 0x |
wasm_module_path must be relative to <data_dir>/extensions/{id}/, got '<p>' | No leading /, no .. |
wasm_module_url must be an https:// URL, got '<u>' | HTTPS only |
wasm_module_path and wasm_module_url are mutually exclusive | Pick one |
Plus the extension-registry id rule: manifest id '<id>' contains invalid characters; expected lowercase alphanumerics + . - _ (extension_registry.rs:45). Note this is stricter than the manifest rule — an id valid in a pack can be rejected as an extension.
12.3 Install failures by status
| Status | Message | Cause |
|---|---|---|
| 400 | <validation error> (set CORECRUXD_EXTENSIONS_ALLOW_UNSIGNED=true to bypass signature requirement in dev) | Validation failed and the bypass is off. The hint is appended only when the bypass is off (http/extensions.rs:209) |
| 409 | extension id already installed | Uninstall first — there is no upgrade in place |
| 409 | manifest_sha256 mismatch for '<id>': registry=<a>, downloaded=<b> | The hosted manifest changed since the curator signed the index. Do not proceed |
| 409 | registry entry mismatch: expected <a>@<b>, manifest is <c>@<d> | The manifest's id or version disagrees with the index row |
| 409 | downloaded module sha256 mismatch: manifest=<a>, downloaded=<b> | The hosted WASM module changed |
| 413 | manifest exceeds the 2097152-byte cap | 2 MiB limit |
| 413 | wasm module exceeds the 16777216-byte cap | 16 MiB limit |
| 502 | manifest fetch failed: <err> / manifest URL returned status <n> | Upstream unreachable or non-2xx |
| 502 | manifest JSON decode failed: <err> | Served bytes are not a manifest |
| 502 | module URL returned status <n> | WASM module URL non-2xx |
12.4 Registry problems
404 on GET /v1/extensions/registry
registry index read failed: <io error> (run `corecruxctl extensions sync` to
populate the cached registry index)
There is no cached index. This is the expected state on a fresh daemon (http/extensions.rs:237).
403 on any registry route
registry index signature verification failed: <library error>
The cached index cannot be verified against the current keyring (http/extensions.rs:395). Usual causes: the curator key was removed or replaced; the index was rotated and needs a re-sync; the file was edited by hand. Re-run corecruxctl extensions sync.
400 registry index JSON decode failed: <err> — the cached file is corrupt. Delete it and re-sync.
400 trusted keyring read failed: <err> — trusted-keys.json is malformed, or a public_key_hex is not 32 bytes. The error names the offending fingerprint (signing.rs:98).
CLI: public_key_hex must be 64 lowercase hex chars — the fingerprint flag and the key flag are different things. --pubkey-fpr is the identifier, --pubkey-hex is 64 hex characters.
CLI sync failed and list-registry shows the old data — correct. A failed verification never overwrites the cache (extensions.rs:282).
12.4a Studio library problems
404 on GET /v1/studio/library or on an install
Studio library index read failed: <io error> (run `corecruxctl studio sync` to
populate the cached Studio library index)
No cached index — the expected state on a fresh daemon (studio_library.rs:128, studio_library.rs:308). Note this is a different cache from the extensions registry: syncing one does not populate the other.
404 template '<id>' not found in the Studio library index — the id is not a row in the cached index. Re-run corecruxctl studio list-library and copy the id field exactly; it is not the display name (studio_library.rs:318).
403 Studio library index signature verification failed: <library error>
The cached index does not verify against the current keyring (studio_library.rs:762). Same causes as the extensions equivalent — curator key removed or rotated, index re-signed upstream, file edited by hand. Re-run corecruxctl studio sync.
Because shape validation runs inside verify(), this 403 also fires on a validly-signed index carrying a malformed row. The library error text names the entry — for example entry 'studio.ops': pack_url must be https://, or loopback http:// for local mirrors (got 'ftp://…') or entry 'studio.ops': version '1.0' must be semver MAJOR.MINOR.PATCH (studio_index.rs:295). The fix belongs to the curator, not the operator.
403 refusing to install (require-signed)
refusing to install '<id>': pack is unsigned. Add the publisher's key to the
trusted keyring (POST /v1/extensions/keys), or set
CORECRUXD_STUDIO_ALLOW_UNSIGNED=1 to bypass the require-signed policy in dev.
Or, when a signature was present but did not validate, signature did not validate: <verbatim library error> in place of pack is unsigned (studio_library.rs:364). Look the inner error up in §12.2. Do not reach for the bypass on a shared daemon — it is process-wide.
409 conflicts
| Message | Cause |
|---|---|
pack_sha256 mismatch for '<id>': index=<a>, downloaded=<b> | The hosted pack changed since the curator signed the index. Do not proceed — ask the curator to re-sign (studio_library.rs:330) |
pack for '<id>' failed verification: <errors joined with "; "> | The pack fails the same checks /v1/studio/pack/verify runs — schema, hashes.manifest, hashes.bundle, studio schema. See §12.8 (studio_library.rs:353) |
library entry mismatch: expected <a>@<b>, pack manifest is <c>@<d> | The pack's own manifest disagrees with the catalogue row, even though the hash matched (studio_library.rs:384) |
no free board id for '<base>' after 64 attempts (also design / page / workspace) | 64 colliding artefacts already exist. Delete some, or install under a different library id (studio_library.rs:594) |
400 pack for '<id>' carries no installable artifacts (no board tiles, designs, workspaces, or pages)
The pack parsed and verified but has nothing to write — commonly a board doc with empty nodes, links and texts, which is skipped rather than written as an empty board (studio_library.rs:408, studio_library.rs:559).
400 pack_url must be https://, or loopback http:// for local mirrors — the row points somewhere the daemon will not fetch from (studio_library.rs:772).
502 / 413 on the pack fetch — pack fetch failed: <err>, pack URL returned status <n>, pack JSON decode failed: <err>, or the 2 MiB cap. Same shape as the extensions manifest fetch.
403 on a per-write category check — the calling passport may not write that console entity. The install stops at the first refusal (studio_library.rs:424); artefacts already written in this call remain.
The install "succeeded" but my board is not where I expected
Read remaps in the response. An install never overwrites: a colliding id is remapped to -2, -3, and so on, and the pre-existing artefact is left alone (§8.8). The response's written[].entity values are authoritative.
Which library entries are installed?
GET /v1/studio/library computes the join from the installed_from provenance stamp on each console fact. If a def lost that key, the entry will report installed: false even though its artefacts exist (studio_library.rs:188). The console's tolerant readers preserve unknown keys, so this only happens if something rewrote the def without spreading it.
12.5 Invoke failures
| Status | Message | Cause |
|---|---|---|
| 400 | passport_fpr required (body field or X-Corecrux-Passport-Id header) | No calling passport |
| 400 | extension '<id>' has unsupported entry.kind <Kind> for tool dispatch | Only external_tool and wasm dispatch |
| 403 | passport '<fpr>' has no grant for extension '<id>' | Issue a grant |
| 403 | tool '<t>' is not in the grant's allowed_tool_names | Widen the grant, or clear the list to mean "all" |
| 403 | scope violation | The dispatcher's own grant re-check failed. Deliberately terse |
| 429 | rate limited (cap N/min) | Sliding 60-second window per (extension, passport) |
| 502 | plain http endpoint blocked (set CORECRUXD_EXTENSIONS_ALLOW_PLAIN_HTTP=true for dev) | Non-HTTPS endpoint |
| 502 | endpoint '<authority>' is not allowed by extension '<id>' network.allowed_hosts | Host or port outside the allowlist. Remember: literal matching, no wildcards |
| 502 | invalid external tool endpoint '<u>' | The URL does not parse or has no host |
| 502 | tool '<t>' not declared by extension '<id>' | Not in the manifest's tools[] |
| 502 | request payload too large: N bytes (max M) | Shrink args |
| 502 | response payload too large: N bytes (max M) | Your response exceeded the effective cap. Check safety.max_output_bytes — it tightens, and may be lower than the daemon default |
| 502 | upstream returned <status>: <body truncated to 512 chars> | Your endpoint returned non-2xx |
| 502 | upstream returned malformed response: <serde error> | Your body is not {result, fact_writes?} |
| 502 | transport: <err> | Connection, TLS or timeout failure |
WASM-specific:
| Status | Message | Cause |
|---|---|---|
| 404 | wasm module file missing at '<path>' | The cached module was removed. Reinstall |
| 409 | module sha256 mismatch: manifest=<a>, on-disk=<b> | The cached bytes were altered. Reinstall |
| 408 | EXT_WASM_FUEL_EXHAUSTED | Raise CORECRUXD_WASM_FUEL_DEFAULT, or make the module cheaper |
| 408 | EXT_WASM_DEADLINE_EXCEEDED | Raise CORECRUXD_WASM_WALL_MS_DEFAULT |
| 507 | EXT_WASM_OOM | Raise CORECRUXD_WASM_MEMORY_BYTES_DEFAULT |
| 501 | wasm extensions require building corecruxd with --features wasm-extensions | Rebuild the daemon |
| 503 | wasm engine init failed at startup; restart the daemon and check logs | Engine construction failed at boot |
| 502 | missing export: extension_call / missing export: memory | The module does not satisfy the contract (§6.4) |
| 502 | response buffer overflow | Your module returned -1; the response exceeded resp_cap (≤64 KiB) |
| 502 | response is not valid utf-8 / response is not valid JSON: <err> | Bad response bytes |
| 502 | module returned reserved code <n> | A negative return other than -1 |
Inside the module, negative host-function returns are ABI codes, not traps: -2 means no grant is bound, -3 means the entity or query prefix is outside your grant, -4 means your buffer was too small, -6 means the function is a stub (§6.5).
12.6 Pack (console) route failures
| Status | Message | Cause |
|---|---|---|
| 403 | integrations are disabled | CORECRUXD_INTEGRATIONS_ENABLED=0 |
| 403 | integration safe mode blocks install | CORECRUXD_INTEGRATIONS_SAFE_MODE=1 |
| 403 | integration safe mode blocks grants | Same switch |
| 400 | capabilities must not be empty | The grant body needs at least one capability |
| 400 | manifest id must match path pack id | Body manifest id versus URL path id |
| 400 | body pack_id must match path pack id | Same for the optional pack_id field |
| 404 | integration pack not found | No builtin with that id and version. Version defaults to 0.1.0 |
Safe mode also rewrites the reported state: every non-first-party pack that is enabled is displayed as blocked (console.rs:3211). The pack still has its grant; the display is telling you the operator posture, not the stored state.
12.7 Grant failures
| Status | Message | Cause |
|---|---|---|
| 404 | extension '<id>' not installed | Install first |
| 409 | grant already exists; revoke first to replace | No update in place |
| 400 | invalid prefix '<p>': community extensions cannot grant access to a privacy-gated prefix | Reserved prefix (§3.3) |
| 400 | invalid extension_id '<id>' | Not lowercase, or over 128 characters |
| 400 | invalid passport_fpr '<f>': cannot be empty | Missing passport_fpr |
| 404 | grant for '<id>' + '<fpr>' not found | Revoking a grant that does not exist |
12.8 Studio pack failures
| Status | Message | Cause |
|---|---|---|
| 400 | studio.schema must be 'crux.studio.v1', got '<x>' | Wrong payload schema |
| 400 | id, version, and publisher_passport_fpr are required | Blank required field |
| 400 | CORECRUXD_STUDIO_SIGNING_KEY_HEX: not valid hex: <err> | Malformed signing key |
| 400 | CORECRUXD_STUDIO_SIGNING_KEY_HEX: must be 64 hex chars (32-byte Ed25519 seed) | Wrong length |
| 400 | pack must be a JSON object | Verify body shape |
| 200 | ok: false, errors: ["hashes.bundle mismatch: declared <a>, actual <b>"] | The studio payload changed after build, or a client reserialised it non-canonically |
| 200 | ok: false, errors: ["hashes.bundle is missing (studio payload is not integrity-bound)"] | The pack was hand-assembled |
| 200 | signature.verdict: "invalid" with a verbatim error | The library message is the diagnosis; look it up in §12.2 |
Verify returns 200 with a verdict for almost everything — check ok, not the status code.
12.9 Auth failures
| Status | Body | Cause |
|---|---|---|
| 401 | "detail": "missing auth scopes", "hint": "set X-Corecrux-Scopes or Authorization: Bearer <scopes>" | Dev mode, no scopes supplied |
| 403 | "detail": "insufficient scopes", "missingScopes": [...] | The named scopes are missing |
| 403 | "code": "PASSPORT_HEADER_MISMATCH" | X-Corecrux-Passport-Id disagrees with the JWT's passport_id |
| 403 | "code": "PASSPORT_HEADER_UNBOUND" | The header was sent under a JWT mode with no passport claim |
Daemon refuses to start with CORECRUXD_AUTH_MODE must be set explicitly; see config.example.env, or on a typo unknown CORECRUXD_AUTH_MODE '<bad>'; valid values: off, dev_scopes, jwt_hs256, jwt_jwks (main.rs:307).
12.10 Diagnostic recipes
# Is the unsigned dev bypass on?
curl -s http://127.0.0.1:14800/v1/extensions \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN" | jq .allow_unsigned_dev
# What does the daemon think is installed, and at what tier?
curl -s http://127.0.0.1:14800/v1/extensions \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN" \
| jq '.extensions[] | {id: .manifest.id, version: .manifest.version, trust_tier, manifest_hash}'
# Which keys will verify a signature? (both rails share this keyring)
curl -s http://127.0.0.1:14800/v1/extensions/keys \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN" | jq .
# Is the Studio library index cached and verifiable, and what is installed?
curl -s http://127.0.0.1:14800/v1/studio/library \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN" \
| jq '{curator: .curator_passport_fpr, tier_enforcement,
entries: [.entries[] | {id, version, installed, installed_entities}]}'
# Scheduler health for every job.
curl -s "http://127.0.0.1:14800/v1/facts/list?entity_prefix=__sync__::" \
-H "Authorization: Bearer $CRUX_AGENT_TOKEN"
# The audit trail, newest last.
tail -n 50 "$CORECRUXD_DATA_DIR/integrations/audit.jsonl" | jq -c '{ts_unix_ms, action, actor, pack_id, outcome}'
# Recompute a manifest hash and compare with the declared one.
jq -S . manifest.json | head -1 # sanity: the file parses
A gap in the audit tail under heavy invoke traffic is expected — the family is capped at 60 events per extension per minute, with one audit_suppressed marker carrying the count (§5.6).
12.11 When the fix is a restart
| Change | Restart needed |
|---|---|
CORECRUXD_EXTENSIONS_* outbound knobs | No — read per call |
CORECRUXD_WASM_* limits | No — read per call |
CORECRUXD_EXTENSIONS_ALLOW_UNSIGNED | No — read per request |
CORECRUXD_STUDIO_ALLOW_UNSIGNED | No — read per request |
CORECRUXD_STUDIO_SIGNING_KEY_HEX | No — read per build call |
CORECRUXD_VAULT_WATCH_* | Yes — activation runs once at startup |
CORECRUXD_INTEGRATIONS_* | Yes — read into config at startup |
CORECRUXD_AUTH_MODE, ports, data dir | Yes |
Granting a file_watcher pack | Yes — the double gate is evaluated at boot |
| Adding a trusted key | No — the keyring is loaded per operation |
| Installing or granting an extension | No |
Ground truth
- crates/crux-integrations/src/lib.rs:58 —
IntegrationErrormessages - crates/corecruxd/src/extension_registry.rs:35 —
ExtensionsError - crates/corecruxd/src/extension_grants.rs:40 —
GrantError - crates/corecruxd/src/extension_outbound.rs:53 —
OutboundError - crates/corecruxd/src/wasm_host.rs:111 —
WasmError - crates/corecruxd/src/wasm_dispatcher.rs:38 —
WasmDispatchError - crates/corecruxctl/src/extensions.rs:38 —
ExtensionsCliError - crates/corecruxctl/src/studio.rs:42 —
StudioCliError - crates/corecruxd/src/http/studio_library.rs:287 — library install status mapping
- crates/crux-integrations/src/studio_index.rs:295 — per-entry shape errors
- crates/corecruxd/src/http/extensions.rs:199 — install status mapping
- crates/corecruxd/src/http/console.rs:3265 — pack error status mapping
- crates/corecruxd/src/auth.rs:857 — 401 body





