Skip to content

Troubleshooting

A symptom → cause → fix reference for the failure modes people actually hit running Jarvis. Each entry links to the page with the fuller explanation — this page is the fast lookup, not the full story.

Capture & connectors

Capture ingests nothing

Symptom: A fresh install runs fine, but cap_items never grows — no messages show up anywhere.

Cause: Capture is disabled by default. The live poll only processes cap_connectors rows where enabled = true, and that collection starts empty — nothing is enabled until you opt in.

Fix: Create an enabled cap_connectors row for the channel you want (kind, enabled = true, config, secret_ref, owner_addresses). See Connectors → Overview for the field reference and Email / IMAP or Discord for a worked example.

Connector last_error="missing credentials (set the secret_ref env vars)"

Symptom: A cap_connectors row is enabled but never ingests; its last_error field reads exactly:

missing credentials (set the secret_ref env vars)

Cause: The row's secret_ref names an env-var group (connector_secrets() in capture_agent/secrets.py), but the corresponding JARVIS_{secret_ref}_{KEY} variables aren't set — poll.py checks for the connector's required keys before polling and writes this exact string when any are missing, then skips that connector (others keep working).

Fix: Set the env vars matching the row's secret_ref, e.g. for secret_ref = "IMAP_MAIN": JARVIS_IMAP_MAIN_USER and JARVIS_IMAP_MAIN_PASSWORD. See Email / IMAP → Env / secret_ref.

IMAP re-scan after a UIDVALIDITY change

Symptom: After a mail server migration or folder rebuild, the IMAP connector suddenly re-scans a whole folder instead of picking up where it left off.

Cause: This is expected, not a bug. The connector's cursor is {"uidvalidity", "last_uid"} per folder; if the mailbox reports a different UIDVALIDITY than the cursor remembers (the server rebuilt its UID space), last_uid is reset to 0 and the whole folder is re-scanned from the start — UIDs are meaningless across a UIDVALIDITY change.

Fix: Nothing to do — it's safe. cap_items dedupes by external_id, so already-ingested messages are skipped and only genuinely new ones get written. Details: Email / IMAP → Errors.

iMessage

authorization denied opening chat.db

Symptom: The iMessage/SMS poll fails with authorization denied when it tries to open ~/Library/Messages/chat.db.

Cause: chat.db lives behind macOS Full Disk Access (FDA) protection. The process running the poll (the terminal app, or the python3 binary) doesn't have it.

Fix: System Settings → Privacy & Security → Full Disk Access → add the terminal app or python3, then re-run. Full setup: iMessage → Full Disk Access required and the host runbook modules/capture/agent/host/HOST_POLL.md.

chat.db rebuilt (restore/migration) — cursor resets

Symptom: After restoring or migrating Messages, the next poll re-scans the entire chat.db instead of just new rows.

Cause: The cursor is {"rowid", "guid"}; each poll checks that the row still at the recorded rowid has the recorded guid before trusting it. If chat.db was rebuilt, ROWIDs get reassigned, the check fails, and the cursor resets to 0 so the whole DB is re-scanned.

Fix: Nothing to do — it's safe. external_id (the message guid) dedupe makes the re-scan write only genuinely new messages; no message is skipped or duplicated. See "How the cursor stays correct" in modules/capture/agent/host/HOST_POLL.md.

WhatsApp

No live capture — only backfill runs

Symptom: There's no kind for WhatsApp in cap_connectors and no way to poll it live.

Cause: WhatsApp has no sanctioned API for a live poll, so this connector is backfill-only by design.

Fix: Export the chat from the WhatsApp app ("Export Chat") and run python -m capture_agent.backfill whatsapp --export <path> --since YYYY-MM-DD. See WhatsApp for the full workflow and export-format gotchas.

Homelab

blocked at apply: no ssh alias for host label …

Symptom: A homelab proposal stays approved but is never applied; an fyi "blocked" card explains why, or you see the outcome directly.

Cause: A proposal's host label comes from the LLM, which reads it off the Grafana alert's instance/host label. The keys in homelab_agent/hosts.py's HOST_ALIASES map must match your real Grafana instance labels exactly. If they don't, resolve_alias fail-closes on every proposal — safe, but nothing gets applied.

Fix: Edit HOST_ALIASES so its keys equal your actual Grafana instance labels. See Homelab → Gotchas: Host-label alignment and modules/homelab/agent/host/HOST_EXECUTE.md.

Homelab double-apply

Symptom: The same proposal or update gets applied twice.

Cause: Neither the executor nor the update-execute loop holds an atomic claim on approved rows. Hand-running execute --host or update_execute --host while the matching LaunchAgent is also loaded and due to fire risks both picking up and applying the same row.

Fix: launchctl unload the matching LaunchAgent before hand-running (e.g. ~/Library/LaunchAgents/com.jarvis.homelab.execute.plist), or only hand-run before installing it in the first place. See Operating → Host agents & launchd → Don't double-apply and Homelab → Gotchas.

Homelab rollback is not automated

Symptom: An applied monthly update verifies unhealthy and nothing rolls it back automatically.

Cause: By design — rollback is manual. The outcome card records previous_image (<repo>@<olddigest>) but no automated rollback runs.

Fix: Pin that digest in the service's compose file and redeploy by hand:

docker compose -f <stack_path> up -d <service>

See Homelab → Gotchas: Rollback is not automated and modules/homelab/agent/host/HOST_UPDATES.md.

Scheduler & CI

Scheduler crashes with Failed to fork exec: no such file or directory

Symptom: The containerized scheduler container fails to run cron jobs, with an error like Failed to fork exec: no such file or directory.

Cause: supercronic's PID-1 subreaper fatally fails to fork-exec on some Docker/Linux-VM setups when supercronic itself runs as PID 1.

Fix: Already handled — compose.yaml's scheduler service sets init: true, which runs tini as PID 1 so supercronic is a non-PID-1 child instead; that disables the problematic reaper behavior. If you hit this error, confirm init: true is still present on the scheduler service in compose.yaml rather than removing it.

Two Docker contract-test errors when the stack is already up

Symptom: Running the tools/checks Python suite reports 2 failing Docker contract tests.

Cause: Environmental — it happens when the local dev stack (./jarvis up) is already running and its container names conflict with the contract test's own docker compose up.

Fix: Stop the stack first, or ignore those 2 if you know why they're there. See Contributing → Dev setup & the gate: Two environmental Docker contract-test errors (mirrors CONTRIBUTING.md §2/§3 at the repo root).

./jarvis up prints "skipped crontab refresh"

Symptom: ./jarvis up (or reset) prints a note like:

note: skipped crontab refresh (host python lacks PyYAML?); using committed backend/generated/crontab

Cause: ./jarvis up regenerates backend/generated/{manifest.json,crontab} from module.yaml schedules before starting the stack, using the host's Python. That step needs PyYAML; if the host Python doesn't have it, the jarvis script's render_crontab() falls back to the already-committed backend/generated/crontab instead of aborting the stack.

Fix: Harmless — the committed crontab is a generated artifact that's always present and kept current by whoever last ran codegen with PyYAML installed. Install pyyaml on the host Python to make the note go away, or ignore it. See Operating → The jarvis CLI.

Embeddings & semantic index

Embeddings fail offline or in CI

Symptom: Building the semantic index fails, or errors mention a missing embedding runtime, when running offline or in CI.

Cause: The default embedding runtime, fastembed, isn't part of the base install and isn't present in CI — it needs a model download the first time it runs (sqlite-vec is the sidecar vector store the index lives in).

Fix: Either install the embedding dependency:

pip install -r modules/conversation/agent/requirements-embed.txt

or use the deterministic fake embedding provider (no download, no network — what CI itself uses):

JARVIS_EMBED_FAKE=1

See Using It → Semantic index → Embedding models and Operating → Configuration.

MCP server

MCP server crashes on the first tool call

Symptom: The jarvis-conversation MCP server starts fine and answers initialize/tools/list, but the process dies as soon as a client calls any tool.

Cause: POCKETBASE_URL, DEV_USER_EMAIL, and DEV_USER_PASSWORD are hard requirements — Config.from_env() raises SystemExit when one is unset. Live dependencies are built lazily on the first tool call, and the tools/call handler only catches Exception, so that SystemExit isn't caught: it terminates the whole server process instead of returning a per-call error.

Fix: Set all three env vars before serving. See Operating → MCP server → Requirements for live answers for the full requirements list and how the other, non-fatal dependencies degrade instead of crashing.