# Troubleshooting

> Fix the common failures: hook errors in every terminal, panels that stop appearing, a bridge that won't start, stale CLI docs, and what crystl doctor reports.

When something breaks, start with one command:

```bash
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](/docs/faq/) covers questions; this page covers breakage.

## Hook failures

crystl's approval panels and turn-end status run on [hooks](/docs/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](/docs/hooks/#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 uninstall` to take the entries out of `settings.json` too.

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:LISTEN` shows 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](/docs/cli/#setup).

## 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](/pricing/) 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

- **`agents` warns 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.json` is missing or unreadable. The "Hooks need review" dialog inside Codex is not an error — see the [FAQ](/docs/faq/#codex-says-hooks-need-review-what-do-i-do).
- **`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 or `python3 -m json.tool ~/.claude/settings.json` finds 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](/docs/cli/#doctor) for reading it, and [filing a bug](/docs/cli/#report) 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](/docs/updating-crystl/) covers rolling forward and what to do when a release breaks.

## Related docs

- [hooks](/docs/hooks/): what the wiring is and why it exists
- [CLI reference: doctor](/docs/cli/#doctor): every flag
- [updating crystl](/docs/updating-crystl/): when a release goes wrong
- [FAQ](/docs/faq/): questions rather than breakage

---
Source: https://crystl.dev/docs/troubleshooting/
