Troubleshooting¶
Symptom-first fixes for an install that is not behaving. Every section follows the same shape: what you see (with the verbatim error string, so search finds it), the most likely cause, the command to run, and a link to the page that explains the mechanism.
Start here — three commands¶
clauster doctor # config, claude binary, login, trust,
# state_dir, port — explained below
curl -s http://127.0.0.1:7621/healthz # liveness (default port 7621)
journalctl -u clauster -n 100 # server log — or: docker logs <name>
Bridge debug logs live under <state_dir>/logs/ (default ~/.clauster/logs/),
one file per spawn named <label>-<timestamp>-<seq>.log. What doctor and
/healthz report is described under
Health checks; how to read a bridge log is in
Reading the bridge debug log.
The dashboard won't load¶
"connection refused" / the page just times out¶
What you see: the browser reports ERR_CONNECTION_REFUSED or times out;
curl says Connection refused.
Most likely cause: Clauster is not running, crashed at startup (see
It won't start at all), or is on a different port
than the one you are hitting (default 7621).
systemctl status clauster # is it running? what did it last print?
curl -s http://127.0.0.1:7621/healthz # from the host itself
ss -tlnp | grep 7621 # is anything listening on the port?
Mechanism: Networking — loopback vs non-loopback.
The page loads locally but not from another machine¶
What you see: curl http://127.0.0.1:7621/healthz works on the host, but
the same port from your laptop or phone refuses to connect.
Most likely cause: Clauster binds 127.0.0.1 by default, so only the host
itself can reach it. Binding a LAN address (host: 0.0.0.0) requires enforced
auth — see the "refusing non-loopback host" refusal under
I can't get in — or a reverse proxy in front of the
loopback bind.
Mechanism: The auth / networking matrix.
The container exits immediately after docker run¶
What you see (in docker logs <name>): one of
Most likely cause: the official image is Alpine-based (musl libc), and you
bind-mounted a glibc build of claude from the host into the container.
The file is present, but the glibc loader it needs is not, so exec fails with
a misleading "no such file or directory".
The fix: mount a musl build of claude instead, or derive your own image
that installs one (the installer needs bash):
A container can also exit immediately because of a config refusal — a
non-loopback bind without enforced auth, or password_required without a
hash. Check docker logs for the strings in
I can't get in.
Mechanism: Installation — Docker · Networking — Docker.
I can't get in¶
Buttons are refused with origin check failed, or a live view won't connect¶
What you see: the dashboard loads and reads fine, but any action (start a
session, trust a folder, restart) returns 403 {"detail": "origin check
failed"}, and/or the live log / terminal view never connects (the WebSocket
closes with code 1008).
Most likely cause: the browser origin you use is not in the Origin
allowlist. Clauster auto-trusts 127.0.0.1/localhost/[::1] only at
config.port, and only when host: is itself a loopback address. A
non-loopback bind — 0.0.0.0, a LAN IP, or the Docker image (which binds
0.0.0.0) — auto-trusts nothing, so even browsing it at
http://localhost:7621 is rejected. A tunnel, reverse proxy, or an SSH
port-forward onto a different local port has the same effect on a loopback
bind.
Fix: list the address exactly as it appears in the browser's URL bar:
This applies whether or not a password is set — the origin allowlist is a
cross-site defence, not an authentication method, so it is enforced even when
auth.enabled is false.
Mechanism: The master switch · Upgrading.
I get a 403 after putting it behind nginx / Caddy / Traefik¶
What you see: the proxy reaches Clauster, but requests are denied with
403.
Most likely cause: trusted-header (forward-auth) mode is misconfigured — the request does not arrive from an IP in the trusted-proxy list, or the identity header the proxy is supposed to inject is missing or stripped. Test directly against the Clauster port (bypassing the proxy) to isolate which side is denying.
Mechanism: Behind a reverse proxy · Trusted-header (forward-auth) mode.
Login redirects in a loop / the session cookie doesn't stick¶
What you see: the password is accepted but you land back on the login
page; every click bounces you to /login.
Most likely cause: the signed session cookie never makes it back —
a proxy stripping Cookie/Set-Cookie, the browser blocking the cookie, or
mixing origins (reaching the same instance via both http:// and https://
or via two hostnames). Try a private-browsing window directly against the
Clauster port; if that works, the proxy layer is eating the cookie.
Mechanism: Behind a reverse proxy · Security — sessions & cookies.
"password_required but no password_hash set"¶
What you see: clauster doctor fails the auth row with
password_required but no password_hash set, and startup refuses with:
auth.password_required is set but auth.password_hash is empty. Generate one
with `clauster hash-password` (or set CLAUSTER_AUTH_PASSWORD_HASH).
Most likely cause: password login is enabled but no hash is configured. Clauster fails closed rather than serving a login form nobody can pass.
Mechanism: Security — passwords ·
auth config reference.
"refusing non-loopback host ... without enforced auth" — it refuses to start¶
What you see: startup aborts (or doctor fails the auth row with
non-loopback host <x> without enforced auth):
refusing non-loopback host='0.0.0.0' without enforced auth. Set auth.enabled:
true together with auth.password_required (+ a hash from `clauster
hash-password`), auth.reverse_proxy.enabled, or auth.api_token_hash (from
`clauster hash-token`) — or, to opt out on a trusted LAN,
auth.allow_unauthenticated_network.
Most likely cause: you changed host to a LAN/all-interfaces address
without configuring an auth method — deliberate fail-closed behavior, since
an open dashboard on a network port hands out control of your machine. Follow
the message: enable one of the listed auth methods, or explicitly opt out
with auth.allow_unauthenticated_network on a trusted network.
Mechanism: Authentication (fail-closed) · Password auth on a non-loopback bind.
It won't start at all¶
"address already in use" — port already taken¶
What you see: startup dies with an OS-level address already in use
error; clauster doctor warns on the port row:
7621 already in use (Clauster already running?).
Most likely cause: another Clauster (often a still-running service unit) or an unrelated process owns the port.
Stop the other process, or change port in clauster.yml.
"not writable" / permission denied writing state¶
What you see: doctor fails the state_dir row with
<state_dir> not writable: ... or
<state_dir> can't be created: <ancestor> not writable.
Most likely cause: the user running Clauster (often a dedicated service
user under systemd) does not own the state directory (default ~/.clauster).
Fix ownership, or point state_dir somewhere the service user can write.
Mechanism: Run as a systemd service.
"no config found" / "invalid config"¶
What you see: doctor fails the config row with no config found: ...
or invalid config: ...; clauster run exits with a config validation error.
Most likely cause: no clauster.yml where Clauster looks for one, or a
YAML/typing mistake in it. The invalid config detail names the offending
key.
Mechanism: Configuration — loading & overrides.
"database migration failed; refusing to start"¶
What you see (server log):
followed by a database migration failed: ... error.
Most likely cause: the state database is ahead of the binary (it was migrated by a newer build and you downgraded), or the file is corrupted. Clauster refuses to run against a schema it does not understand rather than guessing.
The fix: upgrade back to (at least) the version that migrated the DB, or restore a backup — see Recovering from a corrupted state database and upgrading.md.
A bridge won't start, or dies right after starting¶
The card sits on "starting" then goes to "error"¶
What you see: the project card shows starting, then flips to error.
Most likely cause: the bridge spawned but never registered an environment
within startup_grace_seconds — most often a claude login problem (the
bridge starts, then hangs unauthenticated).
clauster doctor # read the claude-login row
tail ~/.clauster/logs/<label>-*.log # the bridge's own account of it
Mechanism: Reading the bridge debug log · Bridge lifecycle.
The card goes straight to "crashed"¶
What you see: the card shows crashed shortly after spawn.
Most likely cause: the claude process exited unexpectedly. The bridge
logs its failure reason to its debug file before exiting, and a spawn that
fails outright also captures a stderr tail — so the card's log view (or the
on-disk log) usually names the cause.
"spawn of ... failed to launch"¶
What you see (server log): spawn of <name> failed to launch: ...
(or pty spawn of <name> failed to launch: ... for an Interactive Session).
Most likely cause: the claude process could not be started at all —
a bad binary path, a permissions problem, or a PATH issue (next section).
The tail of the message is the underlying OS error; start there.
"claude binary 'claude' not found on PATH; Clauster cannot start."¶
What you see: exactly that string, from clauster run or doctor's
claude row.
Most likely cause: Clauster runs in an environment whose PATH does not
include claude — classically a systemd unit (minimal PATH) with claude
installed per-user or via nvm-managed node.
The fix, in order of preference:
- Set
claude.binaryinclauster.ymlto the absolute path of the binary. - Add its directory to the service unit's
PATH(see the systemd notes in Installation). - For nvm-managed node tooling in spawned bridges, see
claude.path_appendandclaude.node_from_nvmin theclaudeconfig reference and npx/node MCP servers under systemd.
The trust prompt keeps blocking the spawn¶
What you see: a bridge spawns but sits waiting on Claude's workspace-trust prompt instead of becoming ready.
Most likely cause: the project directory is not trusted and Clauster's
trust pre-write did not cover it (for example, the project lives outside
projects_root). Trust is ancestor-inheriting: trusting projects_root
covers every project under it.
Mechanism: Security — workspace trust.
Claude itself is the problem¶
"not logged in" / "access token has expired; re-authenticate with claude"¶
What you see: doctor warns on the claude-login row —
not logged in (... missing) — run `claude` to authenticate; bridges inherit
the operator's login or logged out (no access token in ...) — re-run
`claude` — or an operation fails with
access token has expired; re-authenticate with `claude` or
no claudeAiOauth.accessToken in ....
Most likely cause: the account that runs Clauster has no usable claude
credentials. Bridges inherit the operator's login, so a bridge can spawn
and then hang unauthenticated.
The fix: run claude interactively as the service user and complete
login, or use the dashboard login flow
(login_shepherd).
An ANTHROPIC_API_KEY in the service environment also satisfies the check.
"a login flow is already in progress; cancel it first"¶
What you see: the dashboard sign-in panel refuses to start, reporting that a login flow is already in progress.
Most likely cause: an earlier sign-in was started but its authorize link was never used. The shepherd runs one flow at a time, so the abandoned one holds the slot.
The fix: reload the dashboard. The sign-in panel reads the server's state on load, so it re-opens on the in-progress flow with its authorize link and a Cancel button — cancel, then start again. A flow untouched for more than 15 minutes is also reclaimed automatically by the next sign-in attempt.
The claude version is too old (min_version)¶
What you see: doctor fails the claude row with
<version> < required <min_version> — run `claude update` (or reinstall via
the installer) to reach >= <min_version>.
The fix: claude update (as the same user Clauster runs as), then re-run
clauster doctor.
An Interactive Session (pty) bridge is wedged¶
The live log tail is silent or frozen¶
What you see: the dashboard's live log view stops updating.
Most likely cause: the WebSocket stream died, not the bridge. Check whether the on-disk log is still growing:
If it grows, the bridge is fine — reload the dashboard to reconnect the stream. If it is silent too, treat it as a wedged bridge: stop and resume it from the card.
The bridge survived a restart and I can't stop it¶
What you see: after a Clauster restart there is a claude process (and
its keeper) still running with no project card that controls it.
Most likely cause: pty (Interactive Session) bridges are designed to
survive a systemctl restart under the shipped unit's KillMode=process;
normally Clauster reattaches to them at startup. One that was orphaned
instead is cleaned up with:
clauster keepers # list orphaned pty keepers (no project card)
clauster keepers --kill <pid> # stop one by its keeper PID
Mechanism: clauster keepers
· the KillMode / restart caveat.
Is the bridge dead, or is the stream dead? — how to tell¶
Three probes, from cheapest to most direct:
- On-disk log growing? (
tail -fas above) — growing means the bridge is alive and only the dashboard stream is stale; reload the page. - Card status —
crashed/errormean the bridge is gone (the status table is in Reading the bridge debug log). - Process there?
pgrep -a -f 'claude.*remote-control'— a live process with a dead card usually means an orphaned keeper (previous section).
Everything looks fine but the session never responds¶
Hosted / Direct Session: "cannot connect to claustrum"¶
What you see: a Direct Session fails with
cannot connect to claustrum at <state_dir>/claustrum/daemon.sock, or the
API returns hosted channel unavailable: claustrum daemon not connected.
Most likely cause: the claustrum daemon is not running (or not installed — it is an optional dependency), or its socket path is wrong.
clauster deps list # is claustrum installed?
tail ~/.clauster/claustrum/daemon.log # what did the daemon last say?
Mechanism: claustrum config
reference.
"claustrum daemon unavailable at startup"¶
What you see (server log): claustrum daemon unavailable at startup: ...
— Clauster keeps running, but Direct Sessions are unavailable. Related
messages: running claustrum daemon rejected the persisted token and
claustrum daemon connection lost.
Most likely cause: the daemon failed to start or an old daemon is running
with a token Clauster no longer holds. /healthz reflects claustrum health;
restarting Clauster retries the connect-or-spawn cycle.
Reattach failed after a restart¶
What you see: after a Clauster restart, a previously live Direct Session
card shows an error like reattach failed: ... (server log:
hosted: reattach of <id> failed: ...).
Most likely cause: the underlying daemon process ended while Clauster was down, so there was nothing to reattach to. The transcript is not lost — start a new Direct Session for the project and resume from the captured session.
Getting more detail¶
Raising the log level¶
The server log level is not currently adjustable — Clauster runs uvicorn
pinned at info. A verbosity knob is tracked in
#993. Note that the
logs.* config keys control the bridge debug log stream (rotation, ANSI
stripping, redaction), not server verbosity.
Reading the bridge debug log (on-disk vs the redacted stream)¶
The dashboard's live tail is ANSI-stripped and redacted; the on-disk file
under <state_dir>/logs/ is, by default, the verbatim debug log
(logs.redact_session_url: false — redaction happens only over the
WebSocket). Full walkthrough:
Reading the bridge debug log ·
logs config
reference ·
how redaction works.
What clauster doctor checks actually mean¶
Doctor's output has two kinds of rows. The table lists the core rows, present in every run:
| Row | OK means | A warn/fail usually means |
|---|---|---|
config |
clauster.yml found and valid |
no config found: ... / invalid config: ... |
claude |
binary found, version ≥ min_version |
not on PATH, or too old — see above |
claude-login |
usable claude credentials |
not logged in — bridges will spawn then hang |
projects_root |
the directory exists | wrong path in config |
git |
git on PATH |
create/clone features unavailable |
state_dir |
writable | permissions — see above |
auth |
configuration consistent | one of the two auth refusals above |
port |
port free | already in use (Clauster already running?) |
version |
build up to date | upstream has a newer release / stale checkout |
systemd |
KillMode=process (bridges survive a restart) |
a restart would kill live pty bridges |
node-toolchain |
node resolvable for npx MCP servers | nvm-only node invisible to bridges |
workspace-trust |
projects_root is trusted |
an untrusted root — spawns will stall on the trust prompt (see above) |
The rest belong to two prefixed families that grow as optional pieces are added, so read the prefix rather than expecting this page to enumerate them:
extra:<name>— an optional Python extra. Today:extra:pyte(live terminal view),extra:apprise(outbound notifications), and on Windowsextra:pywinpty(Interactive Sessions). A warn carries the exact install hint (e.g.pip install 'clauster[notify]').appriseis shown only when notifications will actually send —notifications.enabledwith at least onenotifications.urlsentry (#1016); with the channel off or no URL configured, runtime never imports apprise, so it isn't a nag worth carrying here.pyteandpywinptyare always shown when missing: pyte also reassembles the bridge connect-URL and pywinpty is the Windows Interactive-Session backend, so they matter beyond the opt-in live-terminal view.binary:<name>— a bundled companion binary managed byclauster deps. Today:binary:claustrum(Direct Session daemon, shown whenclaustrum.enabled) and on Windowsbinary:shawl(service wrapper). A warn shows theclauster deps install <name>remedy. Presence is resolved the same way the daemon spawns, so a configuredclaustrum.binarypath (the minimal-PATH workaround) counts as present rather than warning falsely. For claustrum the row also reports the detected version fromclaustrum --versionand warns (advisorily — never a doctor failure) when it can't be confirmed at or above the release clauster pins; an unstamped local/dev build (claustrum-dev) is expected to warn. A separatebinary:<name>:shadowwarn appears when a manageddeps/bininstall exists but a differentPATH/configured binary wins resolution, so you know which copy actually runs.
Family rows appear only when applicable on your platform and config, and they
warn, never fail: a missing optional piece leaves a feature dormant without
flipping doctor's exit code. clauster deps list shows the same inventory
with install state.
Still stuck — filing a useful issue¶
What to attach¶
clauster --versionand your OS / install method (pip, binary, Docker...).- Full
clauster doctoroutput — it is designed to be safe to share. - The relevant server-log excerpt (
journalctl -u clauster/docker logs). - For bridge problems: the tail of the bridge's
<label>-<timestamp>-<seq>.log— scrubbed first, see below.
The issue tracker is the support channel — see Support.
Scrubbing logs before you post them¶
The on-disk bridge log is not redacted by default
The redaction you see in the dashboard applies to the live stream only —
with the default logs.redact_session_url: false, the on-disk log under
<state_dir>/logs/ is verbatim, including session URLs that act as
bearer-equivalent credentials. Set logs.redact_session_url: true and
reproduce if possible, then review the log and scrub any remaining
session URLs or secrets before sharing it — even with redaction
enabled, the redactor only recognizes known token shapes.
Mechanism: Log redaction.