Troubleshooting
Updated September 18, 2026
When something breaks, start with one command:
crystl doctor
It checks the CLI install, the bridge connection, the hook wiring, the phone relay, and the agent CLIs, and names what it finds. This page walks through the failures people actually hit, what each one looks like, and the fix. The FAQ covers questions; this page covers breakage.
Hook failures
crystl’s approval panels and turn-end status run on hooks installed into ~/.claude. The wiring has two halves that break independently: the hook entries in ~/.claude/settings.json, and the ~/.claude/crystl-hook.sh script they invoke. Every failure below is one half missing the other.
One rule prevents most of them: removing crystl means running its uninstall before you trash the app — the uninstall crystl… button in settings (general → terminal), or crystl uninstall in a shard. Dragging Crystl.app to the trash runs no code, so nothing cleans up after it — and the crystl command is a symlink into the app bundle, so it dies with the app. See removing the hooks.
Every hook event fails with exit 127
The loudest one, and it shows up in every terminal, not just crystl: each Claude Code session prints a hook error for every event it fires, all exit 127.
Exit 127 is bash for “command not found”: the hook entries are present in settings.json, but the script they call is gone. The usual cause is a cleanup tool (AppCleaner and friends) that deleted ~/.claude/crystl-hook.sh with the app but left settings.json alone, since that file belongs to Claude Code, not crystl.
Run crystl doctor. It reports exactly this shape — entries present, script missing — and names both repairs:
- Keeping crystl? Open the app. The installer re-checks the wiring and rewrites the missing script.
- Leaving? Run
crystl uninstallto take the entries out ofsettings.jsontoo.
Uninstalled the app entirely and just want the errors gone? Reinstall it long enough to run crystl uninstall, which removes everything cleanly, or delete the crystl entries from ~/.claude/settings.json yourself.
Hooks point at a removed app
You trashed Crystl.app, and the entries and script are still in ~/.claude. This one is quiet: the script finds no bridge token and no socket, answers Claude Code instantly, and probes nothing, so your sessions run normally and nothing slows down.
Harmless, but pointless — and worth cleaning up. If a crystl command still runs on the machine (another copy of the app, or a live symlink), crystl doctor reports the hooks firing into nothing and points at crystl uninstall. If not, reinstall the app (or pull it back out of the trash) and run the uninstall from there.
A leftover stub after uninstall
crystl uninstall deliberately leaves ~/.claude/crystl-hook.sh behind as a disabled no-op stub, because Claude Code sessions that were already running keep calling that path until they restart, and a deleted file would turn each call into an exit 127. The stub is safe to delete once your sessions have restarted; if hook entries somehow still point at it (a restored settings.json backup, say), crystl doctor notices and says to run crystl uninstall again.
Panels stopped appearing after an update or reinstall
Same wiring, softer symptom: no errors, but approvals stay in the terminal and crystl shards shows a flat idle instead of ✓ declared done. Run crystl doctor and read the claude hook line. A healthy line names the script and how many events are wired; anything else says which half is missing. If doctor is clean, check the session actually started inside a crystl shard — the hooks deliberately do nothing outside one.
The bridge won’t connect
The CLI talks to the app over a local HTTP bridge, so every crystl command needs the app running. When crystl doctor says the bridge failed, read the wording:
- A plain connection error usually means the app isn’t running. Launch it.
- “listener failed to start” means the app IS running but its bridge never bound, and doctor tells you why — typically another process already owns the port. Quit whatever holds it (commonly a second copy of crystl, or an old one that didn’t fully exit), then relaunch.
lsof -iTCP -sTCP:LISTENshows who owns what.
Stale CLI primer
crystl stamps a CLI primer into a gem’s agent docs so agents know how to drive it. After an app update, a gem that has been open the whole time still carries the old version’s primer. Doctor’s primer line compares the two and says so:
warn primer primer 1.4.0, app v1.5.0 — reopen the gem or restart Crystl to refresh
Do what it says: reopen the gem or restart crystl. A primer line saying it was never injected means either the tell agents about the crystl CLI setting is off, or the directory was never opened as a gem.
The related version gap is the CLI binary itself. /usr/local/bin/crystl is a symlink into the app bundle, so it updates with the app; doctor’s cli install line warns when something else sits at that path instead. See another crystl is already installed.
The phone works at home, dies on cellular
Same Wi-Fi uses a direct connection; away from it, the phone comes in over the relay, so this is always a relay question. Doctor’s relay line answers it in order of likelihood:
- off in stored settings — the relay was never enabled. Phones reach this Mac on the same Wi-Fi only.
- no licence key — the relay never dials without one; it’s a Guild feature.
- rejected — the relay refused the key. Re-pairing the phone cannot clear this; doctor prints the detail.
- re-registered under a new mac id — the Mac’s relay identity rotated, and phones paired before the rotation must scan the pairing code again (pair device).
Other doctor lines worth knowing
agentswarns nothing is installed. crystl found no agent CLI on the machine. Install one (Claude Code, Codex) so crystl has something to launch. The healthy line lists each binary with its version — worth a glance when an agent misbehaves right after an update, since agent CLIs update themselves in place.codex hooks. Checked only when Codex hooks are enabled; warns when~/.codex/hooks.jsonis missing or unreadable. The “Hooks need review” dialog inside Codex is not an error — see the FAQ.settings.json is not valid JSON. Doctor can’t judge the hook entries and deliberately leaves the file untouched, since it’s Claude Code’s file with your own configuration in it. Fix the JSON (a linter orpython3 -m json.tool ~/.claude/settings.jsonfinds the spot) and re-run.
When it feels slow
Not a wiring problem, but doctor helps here too: crystl doctor --full appends a numbers-only health snapshot — memory and system pressure, open gems and shards, cache stats, store sizes. crystl status carries the short version. See the CLI reference for reading it, and filing a bug for attaching the same snapshot to a report.
A release broke something
Compare crystl docs changelog against your version first — the fix may have shipped. Updating crystl covers rolling forward and what to do when a release breaks.
Related docs
- hooks: what the wiring is and why it exists
- CLI reference: doctor: every flag
- updating crystl: when a release goes wrong
- FAQ: questions rather than breakage