> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reticle.sh/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Reticle is a dev-only, localhost-only verification layer for AI coding agents. It reads program truth (network, state, console, routing, animations, framework state) from inside a running web app and returns a deterministic verdict with evidence. It is not a screenshot tool and not a browser automation library.
> Only `reticle_act_and_wait` and `reticle_assert` produce a verdict. Every other tool moves or reads the app and proves nothing. A drive that ends without one of those two has no result, however many tools it used.
> A verdict of `verified: "unknown"` is not a pass. It means Reticle drove the app and could not tell what happened. Report it as unknown; never weaken a check to make it pass.
> Package names are scoped `@reticlehq/*` and the CLI is `reticle`. Install with `npx reticle init`. The complete tool surface is on the `/usage` page; `/agent-cheatsheet` is the one-screen version.

# Troubleshooting

> Nothing connected, the click did nothing, the verdict says unknown. The failures people actually hit, and the diagnostic for each.

Most of these were hit while writing these docs. The diagnostics quoted are real output.

## Start here

```bash theme={"dark"}
npx reticle doctor
```

```
reticle doctor
  node         v22.14.0
  chromium     ✓ installed
  daemon       ✓ running on :4400 (pid 24735, v2.7.0)
  bridge port  4400  (your app must dial THIS port — not your dev-server port)
  daemon log   /Users/you/.reticle/daemon-4400.log
  tracing      RETICLE_TRACE=1 on the daemon for per-stage timings in that log
```

One command, the whole setup. If this is clean and things still fail, the problem is in the app rather than the wiring.

## "No browser session connected"

By far the most common. Reticle's own diagnostic is unusually good here. It tells you whether the wiring was ever correct:

```json theme={"dark"}
{
  "sessionCount": 0,
  "why": "no browser session connected — but one WAS connected to this daemon earlier, so the wiring is correct. The tab was closed, navigated away, or hard-reloaded. Ask the human to reopen the app (or run `reticle open`), or reload the tab. You do not have to wait for the human: reticle_lease {action:\"acquire\", url} opens a browser Reticle drives itself."
}
```

Read the `why`. "One WAS connected earlier" means stop debugging your config, just reopen the tab.

If nothing ever connected:

* Is the app running, and open in a browser?
* Is `reticle.connect()` actually executing? Check the dev guard is true.
* **Port mismatch.** This is the big one.

### The port mismatch

Your app dials a port; the daemon listens on one. When they disagree, the app runs perfectly and silently never connects. The browser console says so:

```
[Reticle] could not reach the bridge at ws://localhost:4460/reticle after 3 attempts.
Is the Reticle daemon running on that port? If your app runs in a container/devcontainer/WSL,
the daemon is on a different host — set the WS URL explicitly
(Vite: VITE_RETICLE_WS_URL, or reticle.connect({ url })). Still retrying…
```

That message names the exact port it tried. Compare it with `bridge port` from `doctor` and set the one that is wrong:

```bash theme={"dark"}
VITE_RETICLE_WS_URL=ws://localhost:4400/reticle pnpm dev
```

<Warning>
  The bridge port is **not** your dev-server port. A dev server on 4312 and a daemon on 4400 is
  normal and correct.
</Warning>

## The click did nothing

Check whether the tab is throttled. Every response tells you:

```json theme={"dark"}
"warning": "tab throttled; timer/rAF/pointer gestures may silently no-op — refocus before driving"
```

A backgrounded browser tab has its timers suppressed. Synthetic pointer gestures and timers can silently no-op, so an action reports success and nothing happens.

Three fixes, in order of preference:

1. **Drive a lease**. A context Reticle owns, never backgrounded:

   ```json theme={"dark"}
   { "tool": "reticle_lease", "args": { "action": "acquire", "url": "http://localhost:4312/" } }
   ```

   Release it when you finish: `{ "action": "release", "sessionId": "lease-…" }`.

2. **Focus the tab.**

3. **Fail loudly** instead of pretending. Pass `refuseWhenThrottled: true` to your action.

If the tab is fine, check `occluded` on the element. A click that lands on a modal overlay is the other half of "the button doesn't work":

```json theme={"dark"}
{ "occluded": true, "occludedBy": "div.modal-backdrop" }
```

## "ref no longer resolves to an element"

```
That ref is stale: refs are invalidated whenever the DOM re-renders, so any action that navigated,
opened a modal, re-sorted a list or changed the page invalidates every ref taken before it.
Reticle refuses here rather than clicking whatever now occupies that slot.
```

This is Reticle protecting you, not failing. Re-query for a fresh ref.

Better: use `reticle_act_and_wait { until }` for actions that change the page, so the next ref is taken after things settle.

<Warning>
  Refs never survive a full navigation. A new document means new refs, the same button was `e5`
  before a navigation and `e103` after.
</Warning>

## The verdict says `unknown`

`verified: "unknown"` means Reticle drove the app and could not tell. It is **not** a pass.

The usual cause is a throttled tab: throttling suppresses the quiescence detection used to decide the page has settled, so even a successful action can come back unsettled. Drive a lease and try again.

If it persists on an unthrottled context, that is worth reporting. Reticle's own response says so:

```json theme={"dark"}
"feedback_invite": "Reticle could not tell what happened here. If you expected otherwise, reticle_feedback — an unknown verdict is our defect, not yours"
```

## A negative result you are not sure about

Before concluding "no request fired", read the buffer note:

```json theme={"dark"}
"buffer": { "held": 15, "dropped": 529, "note": "…a negative result here may be a false negative; the evidence may have expired…" }
```

"Nothing found" and "I no longer have what you asked about" are different answers. Grade sooner, or widen the buffer.

## The agent cannot find an element

* `reticle_snapshot { mode: "interactive" }` shows everything actionable.
* Add a `data-testid` for a stable handle.
* Narrow with `scope`. A CSS selector or a ref.

## Assertions are flaky on async UIs

* Use `timeout_ms` on `assert` and `wait_for`.
* Pass the `since` cursor from `reticle_act` so only post-action events count. Without it, an assertion can pass on an event from two clicks ago.

## `reticle_state` returns nothing

`storeNames` empty means no store was registered. `reticle_state` reads what your app registered in `src/reticle-dev.ts`.

<Warning>
  Pass the **store**, not `() => store.getState()`. The getter form is read-only and silently produces
  empty `stateDiffs`. Which reads as "nothing changed" and means "I was never watching".
</Warning>

## Source file is not resolving on React 19

React 19 dropped `_debugSource`. Wire up `@reticlehq/babel-plugin`. Or just use `@reticlehq/vite-plugin` or `@reticlehq/next`, which both include it. Without it you get component identity but no `file:line`.

## `ERESOLVE` installing next to a prerelease

`@reticlehq/next` declares `peer next >=13` and `@reticlehq/react` declares `peer react >=18`. On a Next canary or React RC, npm refuses. See [the prerelease notes](/getting-started#installing-alongside-a-nextjs-or-react-prerelease).

## Restarting things

```bash theme={"dark"}
npx reticle stop     # clean daemon shutdown — no pkill needed
npx reticle status   # confirm it came back
```

<Warning>
  Do not kill port 4400 with `lsof -ti tcp:4400 | xargs kill -9`. That matches the MCP proxy as well
  as the daemon and takes your agent's connection down with it. Use `reticle stop`, or at minimum
  `lsof -sTCP:LISTEN`.
</Warning>

## Sessions piling up

Stale sessions pointing at dev servers you restarted an hour ago look completely plausible in a list, and one of them will get picked.

```json theme={"dark"}
{ "action": "end" }
```

`reticle_session { action: "end" }` when you are done, `{ action: "yield" }` when pausing. Both revive on your next action.

<Card title="Still stuck?" icon="comments" href="/tools-session-and-feedback">
  `reticle feedback --agent` works even when nothing is installed and the daemon will not start.
</Card>
