Skip to main content

crewship doctor

Runs a battery of checks against the local Crewship install and prints one row per check with a status. Designed for the “is everything OK before I file an issue?” workflow.

Exit codes

WARN does not fail the command — you can wire crewship doctor into a healthcheck without false alarms on transient external state (e.g. Sentry endpoint briefly unreachable). FAIL means a core requirement for starting Crewship is missing or broken; treat the non-zero exit as a hard gate.

Check categories

Server-dependent checks (server reachable, episodic recall mode, cli server scheme) resolve the same effective server other commands dial: --server flag → active profile (--profile / CREWSHIP_PROFILE) → CREWSHIP_SERVER env → config. An active profile that has no server: entry fails closed — the scheme check reports INFO instead of silently diagnosing a different host than your API calls use. Database-dependent checks (db migration version, telemetry status, DSN wiring, telemetry endpoint) read the SQLite file on this host, resolved DATABASE_URL → CREWSHIP_DATA_DIR → ~/.crewship — the same order the server itself resolves. The db migration version row prints that path and where it came from, e.g. v20260826190607 (latest) — ./crewship.db (DATABASE_URL).
Until #2109 these checks ignored DATABASE_URL and always opened ~/.crewship/crewship.db. On any host where crewshipd runs with its own DATABASE_URL — every development clone uses file:./crewship.db — doctor reported on a file the instance had never written, and nothing in the output said so.Unlike crewship admin / crewship db, these checks are not gated behind --local and do not refuse when CREWSHIP_SERVER names a server. doctor reports on the machine you run it on, by design; a diagnostic that switched itself off because a server was named would be unavailable exactly when something is wrong. Naming the file it read is what replaces the refusal.

Container runtime

Detects which Docker-compatible runtime is available — Docker, Podman, Colima, OrbStack, Apple Containers, or Rancher Desktop. Auto-probes each candidate socket; the first reachable one wins.

Container runtime gaps

The runtimes Crewship drives are not equivalent, and the differences are measured — a conformance harness runs the product’s own container config against a live daemon and records what each one silently declines to apply. This check reports what the runtime in use will not honour.
  • PASS — the runtime honours every crew hardening control.
  • WARN — a control is dropped. The row names the control; the hint line names the consequence. The only entry today is Podman below 5, which reduces GroupAdd to the primary GID: agents never hold gid 1002, so every crew-shared memory read fails with EACCES. Upgrading to Podman 5+ is the only remedy — the GID cannot be delivered another way through the Docker compat API.
  • INFO — no Docker-API runtime answered (including Apple Containers, which is not one). The container runtime check above already owns that finding; this one does not repeat it.
Advisory by design. A gap is “this runtime has known limitations”, not “your install is broken” — crews start, crews run, and every other control is applied — so it never exits non-zero. The same finding is emitted as a startup WARN in the server log and carried on system info; a warning nobody can reach after boot is why this check exists (#1672).

Port binding

Confirms the HTTP port (default 8080 or CREWSHIP_PORT) is free.
  • PASS — port is bindable.
  • FAIL — port in use. lsof -i :8080 to find the squatter, or change CREWSHIP_PORT.

Database file permissions

Stats ~/.crewship/ directory and the SQLite DB file. Expects:
  • Parent directory: 0700
  • DB file: 0600
  • WAL + SHM sidecars (if present): 0600
Drift here usually means a backup was restored via tar without preserving mode bits. Fix:

NEXTAUTH_SECRET

Surfaces where the JWT signing secret lives (env var vs <dataDir>/secrets.env) and validates the value is at least 32 characters. As of PR #446 the secret is auto-bootstrapped on first start, so a missing value is no longer a failure — doctor reports INFO and the next crewship start generates it.

Episodic recall mode

Reads the episodic field from the running server’s /healthz. Reports whether episodic memory recall runs with a vector embedder or degraded to keyword/FTS only.
  • PASS — vector + sparse recall — an embedder is configured (KEEPER_OLLAMA_URL); the boot-time indexer sweeper is embedding journal entries and recall serves vector + BM25 results.
  • FAIL — vector-degraded — an embedder is configured but its calls are failing, so the index is not growing and recall is silently BM25-only. The server includes the underlying error as episodic_error and this check prints it. The usual cause is an Ollama host that is reachable but not serving the model — ollama pull nomic-embed-text on that host. This is a FAIL rather than a WARN because it is worse than sparse-only: that one is a deliberate choice that still serves keyword recall, whereas this looks configured and delivers nothing.
  • WARN — sparse-only — no embedder configured. Recall still works on keywords, but vector similarity is off. Set KEEPER_OLLAMA_URL to an Ollama host serving nomic-embed-text.
  • INFO — server not reachable / older server — the daemon is down (the server reachable check already FAILs for that) or predates the episodic health field.

Legacy crew resources

Calls the authenticated GET /api/v1/admin/legacy-resources endpoint to detect orphaned pre-C1 (slug-only) crew docker resources that survive crewship seed --nuke and make every agent in the affected crew fail to start — surfaced to users only as a generic “failed to start agent container”. (Detection runs on this admin endpoint rather than the unauthenticated /healthz path, so a slow docker daemon can’t stall health probes.)
  • PASS — clean — no orphaned legacy volumes/containers for any current crew slug.
  • WARN — present — at least one orphaned pre-C1 resource exists. Agents in the affected crew(s) will fail to start. Run crewship admin prune-legacy to remove them.
  • INFO — not logged in / unreachable / non-docker server — the check needs an authenticated session (crewship login); or the daemon is down (the server reachable check covers that) or the container provider isn’t docker.

Telemetry status

Reads app_settings.telemetry_opt_in from the local DB. Reports:
  • PASS — ENABLED + DSN — telemetry is on and a DSN is wired in. Shows the endpoint host (vendor default vs CREWSHIP_SENTRY_DSN override).
  • PASS — DISABLED — operator opted out via crewship telemetry off.
  • WARN — ENABLED but no DSN — consent recorded but no DSN available. Local dev builds without ldflag injection; not an error, just informational.
  • WARN — not configured — fresh DB, never asked. Will default to ENABLED on the next crewship start (v0.1 beta behaviour).
See Telemetry for opt-out and routing-override recipes.

DSN reachability

Only runs when telemetry is enabled AND a DSN is set. Best-effort TCP connect (5s timeout) to the resolved endpoint at port 443.
  • PASS — endpoint reachable; events will ship.
  • WARN — endpoint unreachable. Crashes won’t ship until network heals. Not a Crewship health signal — could be local firewall, DNS issue, or Sentry outage. crewship doctor does not fail on this; you’d be silently angry at the wrong layer.

Update available

Calls the GitHub Releases API for the project (cached 24h locally).
  • PASS — running latest stable, or running a newer pre-release.
  • WARN — newer stable available; output shows current → latest with install hint (brew upgrade crewship or docker pull).
  • INFO — running a dev build, version check skipped.
Suppress with CREWSHIP_SKIP_UPDATE_CHECK=1.

Example output

PR #441 added a version + OS/arch header banner and a “Next steps” footer that points the operator at the three commands that actually move them forward (or at the troubleshooting page on FAIL). Presentation-only — no check behaviour changes.

Output formats

  • --no-color — disable ANSI colors. Auto-detected when stdout is not a TTY (so log-collection scripts get plain text without any flag).
  • Status codes (PASS/WARN/FAIL/INFO) are always in left-padded brackets and parseable with awk '{print $1}'.

Auto-repair (--fix)

--fix opts into safe, narrowly-scoped repair side-effects during the check run. Currently scoped to:
  • Database directory missing — creates $CREWSHIP_DATA_DIR (or ~/.crewship) with 0700 perms instead of failing the check. The detail column then reads (created via --fix) so you can see what changed.
Anything destructive (file overwrites, mode-bit flips on existing files, DB schema repairs) is intentionally out of scope — --fix is for first-run onboarding, not running surgery on a populated install.

When to run

  • Before opening a GitHub issue — attach crewship doctor --no-color output. Saves a back-and-forth diagnostic round.
  • After upgrading the binary — pre-migration snapshot has run by this point; doctor confirms the new binary is happy with the existing DB.
  • In CI smoke tests — the .github/workflows/smoke-test.yml workflow runs crewship doctor against the freshly-released binary on each release tag to catch broken binaries before they reach users.

crewship doctor cleanup

Read historical container cleanup diagnostics from the local SQLite database, including failures for a workspace that has been deleted. Local file permissions authorize this read; no server login or surviving workspace membership is needed. The command never contacts Docker, creates a database, applies migrations, or removes a resource. DATABASE_URL selects the local file, as with other doctor probes. An old database without the diagnostic tables reports an error.
JSON contains scope=containers, observation=persisted, and items with installation/crew IDs, state, last observation, completeness, eligible remaining count, legacy unattributed count and fixed error code. Observations older than 90 seconds read as stale unknown. Persisted snapshots are not live host clearance and do not certify cleanup of volumes, host data or images.