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.