npx @reticlehq/server doctor first. It checks Chromium, the daemon, the port and the connected sessions in one command, and it names the problem in most cases. If sessions reads zero, your app is not reaching the bridge, and a port mismatch is the usual reason.
Reticle is a verification layer that runs a dev-only SDK inside your running web app. Two halves have to be working: your agent needs the MCP server registered, and your app needs the SDK connected to the local daemon. Almost every failure below is one of those two halves being absent.
Most of these were hit while writing these docs. The diagnostics quoted are real output.
Start here
sessions line is the one to read first. Zero connected pages is a failing check, not a footnote, because it is the state a setup most often stalls in: everything installed, the daemon up, and nothing having dialled in. When it is zero, doctor prints the same diagnosis described below underneath the checklist, so you do not have to call a tool to see it.
”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:why. If it says one was connected earlier, stop debugging your config and just reopen the tab. If it says nothing ever connected, the wiring itself is wrong, so work through the list below.
If nothing ever connected:
- Is the app running, and open in a browser? If nothing is serving it, see Who starts the dev server: this is the agent’s job, not a question to put to you.
- Is
reticle.connect()actually executing? Check the dev guard is true. - Port mismatch. This is the big one.
Who starts the dev server
The agent does. If no dev server is listening, the agent reads your project’s own dev script out ofpackage.json, runs it in the background, tells you in one line that it is running and how to stop it, and carries on. It used to be told the opposite, “Reticle never starts the dev server for you, that’s your job”, and agents obeyed it exactly: they read the rule, declined to start anything, and ended the turn with nothing verified.
The daemon still does not do it, on purpose. A build process started by a long-lived background daemon is invisible to whoever’s machine it is running on, was never consented to, cannot easily be stopped, and orphans when the daemon exits. It would also mean a piece of standing infrastructure runs arbitrary package.json scripts. An agent already has shell access in your repo, already runs install and build commands, and runs inside a host that asks you before it does. A dev server the agent starts is in the transcript, attributable to a step you approved, and stoppable.
The agent does not have to work out the command. When reticle_sessions comes back with nothing connected, it carries a next_action naming which of four cases this is and, where one applies, the literal command and port: start the dev server, run reticle init, open the app, or reopen a tab that went away. The command is read from this project’s own scripts, so a project with no recognisable dev script gets no command rather than a guess.
The guards, which apply wherever this instruction appears:
- Never start a second one. If something is already listening on the app’s port, it is adopted.
- Never guess the command. It comes from
package.jsonscripts. If there is no recognisable dev script, the agent says so and stops rather than inventing one. - Never kill anything: not a dev server, not a daemon, not a port holder, including one it started.
- Background it, and say so. You must know a server is running and how to stop it. A dev server you do not know about is the same failure one step later.
- The permission prompt is your agent host’s. Nothing in Reticle bypasses or auto-approves it, and you can always decline.
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_lease, which is the path init steers it to, the daemon reads that address off the page’s own console, compares it with the port it is bound to, and puts the mismatch in the tool result:
bridge port from doctor and set whichever is wrong:
The click did nothing
Check whether the tab is throttled. Every response tells you:-
Drive a lease. A context Reticle owns, never backgrounded:
Release it when you finish:
{ "action": "release", "sessionId": "lease-…" }. - Focus the tab.
-
Fail loudly instead of pretending. Pass
refuseWhenThrottled: trueto your action.
occluded on the element. A click that lands on a modal overlay is the other half of “the button doesn’t work”:
“ref no longer resolves to an element”
reticle_act_and_wait { until } for actions that change the page, so the next ref is taken after things settle.
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:
A negative result you are not sure about
Before concluding “no request fired”, read the buffer note:The agent cannot find an element
reticle_snapshot { mode: "interactive" }shows everything actionable.- Add a
data-testidfor a stable handle. - Narrow with
scope. A CSS selector or a ref.
Assertions are flaky on async UIs
- Use
timeout_msonassertandwait_for. - Pass the
sincecursor fromreticle_actso 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.
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.
Restarting things
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.reticle_session { action: "end" } when you are done, { action: "yield" } when pausing. Both revive on your next action.
Frequently asked questions
Why does reticle_sessions return nothing when my app is clearly running?
Why does reticle_sessions return nothing when my app is clearly running?
Your app running is not the same as your app being connected. The SDK has to dial the daemon’s
bridge port over a WebSocket. If
npx @reticlehq/server doctor shows the daemon up and
sessions at zero, either reticle.connect() never executed (check the dev guard is actually
true) or the app dialled the wrong port. The browser console names the exact address it tried.Which port should my app dial?
Which port should my app dial?
The bridge port, which defaults to
4400. It is not your dev-server port. A dev server on
4312 and a daemon on 4400 is the normal, correct arrangement. doctor prints the bridge port
under bridge port.I restarted my agent and the reticle tools still are not there.
I restarted my agent and the reticle tools still are not there.
If you registered the MCP server while the agent was running, restart the agent process itself:
quit and reopen Claude Code, reload the window in Cursor, or press Start in
.vscode/mcp.json in
VS Code. /mcp only manages servers that were already loaded, so it cannot discover a new one. If
the tools are still missing, run claude mcp list and check reticle is present.Does a verdict of unknown mean the test failed?
Does a verdict of unknown mean the test failed?
No, and it does not mean it passed either.
verified: "unknown" means Reticle drove the app and
could not tell what happened, usually because the page never settled inside the observation
window. Report it as unknown. If you hit one on an unthrottled context and believe it is wrong,
that is a Reticle defect worth sending with reticle_feedback.Is it safe to kill port 4400 to restart things?
Is it safe to kill port 4400 to restart things?
No.
lsof -ti tcp:4400 | xargs kill -9 matches your agent’s MCP proxy as well as the daemon and
takes the connection down with it. Use npx @reticlehq/server stop, or at minimum add
-sTCP:LISTEN so only the listener matches.Still stuck?
reticle feedback --agent works even when nothing is installed and the daemon will not start.