Troubleshooting
Most beta-era problems fall into a small set of buckets. Start here before opening an issue.First step: crewship doctor
doctor runs a battery of checks and prints one row per check with a
status: PASS / WARN / FAIL / INFO. Read each WARN/FAIL — the
hint text in the second column is the remediation.
doctor covers (as of v0.1 beta):
- Container runtime detected — Docker / Podman / Colima / OrbStack / Apple Containers. If FAIL, install one (see Install — container runtime requirement).
- Server reachable — TCP-dials the configured server host:port
(default
localhost:8080) so you can tell “server down / wrong port” apart from “auth issue”. - Database file permissions — confirms
~/.crewship/crewship.dbis0600(owner-only). Drift here usually means a backup was restored viatarwithout preserving the mode bit. - Telemetry status — shows current opt-in state and resolved
endpoint host (vendor default vs
CREWSHIP_SENTRY_DSNoverride). - DSN reachability — best-effort TCP connect to the Sentry endpoint:443 (5s timeout). Sentry being unreachable is not a Crewship health signal — WARN here just informs you that crashes won’t ship until the network heals.
- Update available — checks GitHub Releases API for a newer version. WARN if a stable release is newer than your installed build.
Which container runtime is Crewship actually using?
Admin → Runtime lists every container runtime present on the host — each with its own product mark, its own daemon’s version, and its own socket — and marks the one this server is driving In use. Everything else is badged Detected: installed, not driving anything. The panel is read by an admin (the endpoint redacts socket paths and daemon versions belowADMIN), and refreshes when you press
Re-detect. It does not poll: each check re-probes every candidate
socket, and the answer only changes when someone installs a runtime.
Two distinctions the panel makes that nothing else can:
- The product, not the socket. OrbStack, Rancher Desktop and Docker
Desktop all offer to point
/var/run/docker.sockat their own daemon. Detection follows the link, so a machine running OrbStack says OrbStack, not Docker. - Present vs in use. If runtimes are listed but none says In use,
this server started without a container provider —
--no-docker, or one that failed to start. Agents cannot run until it has one, and that is a different problem from having no runtime installed.
runtime / version / socket summarise the entry with in_use set,
and are null when runtimes are installed but none is in use. One
daemon produces one entry even when it answers on several paths.
install_links lists every runtime Crewship can drive, so the runtimes
you have not installed are still named; containerd and nerdctl are
deliberately absent, because containerd serves a gRPC API that Crewship’s
Docker client cannot speak.
Symptom catalog
Find your symptom below; each panel expands to its diagnosis and fix.A run says failed with `agent reported a failed run`, but the CLI exited 0
A run says failed with `agent reported a failed run`, but the CLI exited 0
error instead of completed, because the alternative is a mission or routine continuing on an empty answer while the run looks green and still gets billed.The error message quotes the CLI’s own text. To confirm and find the cause:- Open the run in the journal and find the
exec.commandend entry. An in-band failure has severitywarnwithexit_code: 0andin_band_error: true— that pair is the fingerprint. - Read the
resultevent’s metadata on the same run; it carries the CLI’s terminal envelope with usage and cost.
result_summary for the same reason. In a routine, the step fails and — under the default on_fail: escalate_tier — walks to the next fallback tier. It is not retried on the same tier: an in-band failure is deterministic, so retrying it just bills for the same answer (details).Common causes by message:turn cap (N turns)— the agent ran out of agent-loop turns mid-task. Raisemax_turnson the step, or split the work. Note that the step still walks the fallback-tier chain, so a task that flails for 20 turns can replay them on a pricier model;on_fail: abortstops that.- a refusal (“I cannot help with…”) — reword the task, or lower the tool profile’s blast radius.
- a usage / quota message — check Paymaster and the credential’s plan.
- an internal CLI error — the vendor CLI in the crew image may be stale; reprovision the crew.
Interactive `crewship ask` / `run` fails with `ws read: EOF`
Interactive `crewship ask` / `run` fails with `ws read: EOF`
101 succeeds, then the socket closes), the CLI now prints the server’s reason instead of a bare ws read: EOF — for example:invalid or expired ws-token— the short-lived WS ticket (15 min) expired or didn’t validate. Just retry; the CLI mints a fresh ticket per run. Persisting means clock skew or a JWT-secret mismatch between the CLI’s server target and the server.no auth message received … (handshake timed out)— the upgrade succeeded but the client’s auth frame never arrived in time. Usually a proxy buffering the first WS frame, or a stale CLI. Check any reverse proxy in front of/wsstreams frames without buffering, and runcrewship self-update.session_revoked— your browser session was revoked; re-authenticate.
WARN line — grep the server log for ws connection rejected after upgrade to see the reason and remote_addr. If you also see a version-skew warning when the CLI fetches its ws-token, upgrade the stale side first.A run fails with "stream ended without a message_stop event", "…without a finish_reason", or "…without a done:true event"
A run fails with "stream ended without a message_stop event", "…without a finish_reason", or "…without a done:true event"
message_stop for Anthropic, no finish_reason for OpenAI-compatible backends, no NDJSON line with "done":true for Ollama) looked identical to a normal completion, and the run silently succeeded with truncated content: half-written code, cut-off JSON, no error anywhere. For Ollama it was worse than mislabeled success — the aggregated response content came back completely empty even though tokens had streamed in, because it was only assembled once the terminal line arrived. All three now surface as a real failure instead.The error message always ends in unexpected EOF, which the pipeline’s transient-error matcher recognizes — the same task tier is retried automatically the way a 500/502 from the provider would be. You only need to act if it keeps failing after retries: check the provider’s status page, or (for a custom openai_compat endpoint, or a self-hosted Ollama behind a proxy) confirm the proxy isn’t buffering/timing out chunked responses before the model finishes.This is unrelated to a deliberate cancellation (stopping a run, or a timeout) — those still surface as a cancellation, never as this message.`crewship start` exits immediately, no error
`crewship start` exits immediately, no error
--verbose switches the logger to debug level. Look for failed to run migrations or failed to open database. Common fixes:- Permission error:
chmod 700 ~/.crewship && chmod 600 ~/.crewship/crewship.db. The migrate path enforces 0600 on the DB file but can’t reset 0700 on the parent. - Disk full:
~/.crewship/crewship.dblives next to a pre-migration snapshot (*.pre-migrate-*.bak). If the disk is full, the snapshot can’t be written and the boot aborts to prevent a half-migrated DB. Free space, then re-run.
Agent containers won't start
Agent containers won't start
crewship start (and crewship doctor) distinguish two situations and
print the matching fix for your OS:open -a Docker (Docker
Desktop), colima start, container system start (Apple Containers),
or sudo systemctl start docker on Linux.podman machine all run the
daemon inside a VM that only sees the host directories it was configured
to share — Colima shares $HOME by default and nothing else. Crewship
keeps every host path it hands the daemon under its data dir
(~/.crewship by default, inside that share) precisely so a stock install
works without touching the VM’s mount list. If you move the data dir with
--data-dir / CREWSHIP_DATA_DIR, put it somewhere the VM shares — see
“A bind mount fails for a file that exists” below.On Linux, curl -fsSL https://get.docker.com | sh, then
sudo systemctl enable --now docker and add yourself to the docker
group (sudo usermod -aG docker $USER, log out and back in). Or
install podman. On Windows, install Docker Desktop with the WSL 2
backend. Crewship picks whatever socket it finds — no further config
needed.A bind mount fails for a file that exists ("bind source path does not exist")
A bind mount fails for a file that exists ("bind source path does not exist")
podman machine run the daemon inside a VM,
and a bind source is resolved inside that VM. A path the VM does not
share does not exist as far as the daemon is concerned.Crewship names this for you: the error says which runtime, which path, and
the command that shares it — e.g.crewship-sidecarandentrypoint.share staged automatically. They are resolved next to thecrewshipbinary (/opt/homebrew/...for Homebrew,/usr/local/binforinstall.sh) — outside every default VM share set. Crewship copies them into<data-dir>/output/.runtime/at startup and binds the copies, so the only host subtree the daemon has to see is the one it already needed for/workspace,/outputand/crew. Nothing to configure.- The remaining case is a data dir outside the share set. If you point
--data-dir/CREWSHIP_DATA_DIRat, say,/tmpon macOS (which is/private/tmp, and is not the VM’s own/tmp) or a path on an external volume, add that directory to the VM’s mounts or move the data dir back under$HOME.
Switching container runtimes leaves the old daemon's crew containers running
Switching container runtimes leaves the old daemon's crew containers running
DOCKER_HOST
moves Crewship to a different daemon — it does not stop what is running
on the old one. Those containers keep their credentials and stay
bind-mounted to the same host /crew, /workspace and /output
directories the new containers use, so an agent inside one can go on
writing the crew memory the live crew reads back.At startup Crewship now enumerates every other Docker-API daemon reachable
on the host, finds crew containers belonging to this instance (matched
on the instance’s container prefix), and stops them, logging what it
stopped and where:docker stop command
instead of running it. The container keeps write access to live crew memory
until you do.Chat shows a specific container-start error (not just a spinner)
Chat shows a specific container-start error (not just a spinner)
code in the event metadata, instead of a generic “failed to
start agent container”. The full raw cause is in the run journal / server logs.Server logs "stale crewship-sidecar bind-mounted into crew containers" at startup
Server logs "stale crewship-sidecar bind-mounted into crew containers" at startup
CREWSHIP_SIDECAR_PATH binary against its
own build at boot. This WARN means the sidecar is older than the running
server, so it was not rebuilt/recopied for this deploy — crew containers are
bind-mounting a stale sidecar, and sidecar-side features shipped since (egress
client, the memory-auth chokepoint, token_fp used by orphan-container reaping)
may be running from an older binary.Fix — rebuild and recopy the sidecar to the path the server actually uses, then
restart agents so new containers pick it up:dev.sh
rebuilds the sidecar alongside the server on every start and forces
CREWSHIP_SIDECAR_PATH to that fresh, co-located binary (ignoring any stale pin
in .env.local, since #1402), so this warning should not normally appear
there — if it does, the co-located build step itself was skipped or failed;
re-run the deploy and check the build output."Agent exited with code 123"
"Agent exited with code 123"
CLAUDE_CODE_OAUTH_TOKEN — the agent CLI failed its startup auth
check. Fix:crewship agent debug <id> for the container’s resolved PATH.`curl … install.sh | bash` fails with "Bad substitution"
`curl … install.sh | bash` fails with "Bad substitution"
bash, not sh:set -euo pipefail and array-expansion guards that
Debian/Ubuntu dash (the default /bin/sh) does not implement. See
the bash note in Install.`brew install crewship-ai/tap/crewship` fails with "tap not found"
`brew install crewship-ai/tap/crewship` fails with "tap not found"
curl | bash install.macOS Gatekeeper blocks the binary
macOS Gatekeeper blocks the binary
brew install path
strips quarantine automatically; only direct downloads need this.Linux: daemon won't survive logout
Linux: daemon won't survive logout
crewship start as a systemctl --user service (or a tmux-less SSH
session) is killed when the user logs out, because systemd-user
sessions are tied to the login by default.Enable lingering so the user manager keeps running:loginctl show-user "$USER" | grep Linger= reports Linger=yes once
it’s on. Headless servers (no interactive shell) are the common case;
desktop installs that always have a logged-in graphical session can
skip this step.Windows: SmartScreen blocks the binary
Windows: SmartScreen blocks the binary
crewship.exe → Properties → check Unblock → OK.
One-time per binary — subsequent runs skip the prompt. The Homebrew
and Docker Compose install paths bypass this entirely."Migration version N collision"
"Migration version N collision"
YYYYMMDDHHMMSS), so two branches can no
longer reach for the same one. See Migrations.Web UI loads but API requests fail with 401
Web UI loads but API requests fail with 401
crewship.db deletion), visit
/bootstrap to recreate the initial admin account.A delegated assignment shows "working on the task…" forever
A delegated assignment shows "working on the task…" forever
[Assignment] @agent is working on the task… in the lead’s chat), the sub-agent run lives in the server
process. If the server crashes or restarts mid-run, that run is gone —
and before v0.1.x the assignment row stayed RUNNING forever, which
also blocked one of the crew’s concurrency slots so every later
delegation for that crew queued indefinitely.This is now self-healing, on two layers:- On restart, the server fails every assignment that was
RUNNINGunder the previous process with the reasoninterrupted by server restart. The lead’s chat resolves with the failure, the slot is freed, and any queued assignments behind it are dispatched immediately. - While running, a background sweeper (every 5 minutes) fails any
assignment stuck in
RUNNINGwith no completion past its staleness bound: the target agent’s configuredtimeout_secondsplus a 15-minute grace margin, with a 2-hour floor. Because the bound tracks each agent’s own timeout, a long-but-healthy run is never touched — even one configured to run longer than the floor; the sweeper only reclaims slots leaked by abnormal conditions (e.g. a force-killed container that never reported back).
assignment.failed
entries attributed to assignment_recovery, so a post-mortem can tell
recovery-failures apart from real agent failures."Sentry endpoint unreachable" in doctor
"Sentry endpoint unreachable" in doctor
A freshly recreated routine shows old FAILED invocation history
A freshly recreated routine shows old FAILED invocation history
routine delete is a soft-delete), older builds resurrected the tombstoned
database row — and with it the previous routine’s invocation count, last-run
status, and journal entries. A brand-new routine could appear in the UI with
“10 invocations, last FAILED” before it had ever run, typically right after
crewship seed --nuke or when an agent re-authors a routine you deleted.Current builds purge the stale counters and journal entries when a deleted
slug is reused, so the recreated routine starts clean. On older builds the
inherited history is cosmetic — it corrects itself after the routine’s next
real run.Where to go next
Ifcrewship doctor is green and you still hit issues:
Open a GitHub issue
crewship doctor --no-color output and the relevant log
section.