# Troubleshooting Diagnose configuration, protocol events, execution and playback separately. Use [architecture](architecture.md), [tool calling](tool-calling.md) and [context injection](context-injection.md) to identify which path is active. ## Check configuration and connection first Inspect the server configuration without printing secret environment files: ```bash curl -fsS http://localhost:8420/api/config | jq ``` Confirm `s2sUrl`, `lb`, `reasoningType`, `injectProfile`, `apipilot`, `theme` and `startSessionMode`. A direct WebSocket URL bypasses the local relay's server-side context injection and marker execution. For a local proxy, confirm the server can resolve and reach `S2S_PROXY_TARGET`. In browser DevTools, inspect the WebSocket handshake and `session.created`, `session.update` and error events. Check that instructions and native tools are inside `session`. A cached browser module can disagree with current server code; inspect served asset versions and reload with cache disabled when needed. ## Launcher and hot reload `start_ui.sh` serves Vite on `8420` and FastAPI on loopback port `8421` by default. Check both processes in the launcher/PM2 logs. If either exits, the launcher stops the other and returns a failure. Port conflicts are errors; use distinct `UI_PORT` and `S2S_BACKEND_PORT` values for additional worktrees. A Vite "Blocked request" response means the public hostname is missing from `S2S_DEV_ALLOWED_HOSTS`. Configure it without a scheme or port and restart the launcher. If the page loads but edits do not appear, check the browser's `/__vite_hmr` WebSocket and the reverse proxy's upgrade handling. Speech uses the separate `/v1/realtime` WebSocket on the same public origin. For a startup dependency error, check Node/npm on the process manager's PATH, Python venv support, and `setsid`/`curl`. The first start installs dependencies; later starts reuse them until frontend manifests change. Changes to root `.env` or launcher settings require a restart. ## The assistant promises a tool but nothing happens Follow the actual execution mechanism: 1. For a native tool, confirm it appears in the outbound `session.tools` array. 2. Find `response.function_call_arguments.done` with a tool name, `call_id`, and JSON-string arguments. 3. Confirm the browser dispatched `toolcall` and the executor sent its request. 4. Confirm `function_call_output` and any required `response.create` were sent. 5. For ApiPilot, inspect discovery, marker detection, authorization/schema validation, execution, and second-pass result injection instead. A spoken sentence such as “Voy a buscar el reporte” does not satisfy step 2. If the function-call event is absent, inspect model/backend tool generation before debugging a browser executor that has not been invoked. Earlier local debug notes recorded a reproduction where tools and required tool policy were sent but the model produced only spoken text. Those notes refer to the retired MCP ranking path; they do not establish a current ApiPilot failure. Both local investigation notes remain marked `OPEN`. Their useful lesson is to collect evidence at each boundary and reproduce against current code before claiming a root cause is resolved. ## The assistant misses caller or report context - Verify the page has an encoded `phoneNumber` or the intended `episode_id`. - Inspect `/api/user/phone` and context-fetch logs for HTTP/JSON failures. - Confirm profile injection is enabled; a visible profile alone does not prove realtime instructions contain it. - Reports need `S2S_INJECT_REPORTS=true`; `minimal` sends only labels and dates. A historical script will not be in the prompt in that mode. - Static summary text overrides the HTTP summary endpoint. - Inspect the final `session.update` rather than only its browser base prompt. The proxy can append and deduplicate context after the browser sends it. `S2S_DEBUG_PROMPT=true` enables complete prompt logs, including caller information and conversation text. Use summaries when full payloads are unnecessary. The browser's application functions are module-scoped; do not assume names such as `buildInstructions()` are available directly on `window` in DevTools. ## Short gaps or clipped words around tool results The retained audio investigation found very low local serialization/render times for one large tool result, while still observing audio interruptions. That evidence ruled out a UI-render bottleneck for that reproduction; it does not rule out rendering costs for all future payloads. Useful checks are: - Measure tool-result size, prompt length, serialization time and UI render time. - Inspect whether result injection overlaps an active or pending response. - Check whether a speculative response starts before its `response.created` reaches the browser. Cancellation based only on an existing response ID can miss that window. - Inspect deferred text/response queues and whether they flush after `response.done` rather than interrupting the active turn. - Inspect playback prebuffering and queue underflow, especially when the first words of every sentence are affected. - Confirm oversized tool results are summarized or capped on the relevant path rather than assuming every tool payload gets the same treatment. The historical investigation introduced a 90ms playback prebuffer and reduced MCP prompt reinjection. The old 150ms MCP fallback timer is historical, not a current ApiPilot configuration knob. Verify the live worklet and current relay before changing audio timing based on those observations. ## External reasoning and startup `/api/reasoning` returning 409 is expected in `PROXY` mode. In `NODE_RED`, check that `S2S_REASONING_NODE_RED_URL` is reachable from the server and whether the response is `log_only=true`. An empty answer is valid only in log-only mode. In `OPENAI`, check the configured endpoint/model/key and provider logs; HTTP reasoning does not use the native session's tool array. For `TALK`/`TALKFIRST`, the two-second warmup does not override browser microphone/audio permissions. If automatic startup fails, use the unlocker gesture and inspect the permission error rather than increasing the timer without evidence. ## Local validation From the repository root, run the frontend checks and local backend fixtures: ```bash npm --prefix s2s-frontend run check npm --prefix s2s-frontend run build .venv/bin/python -m unittest discover -s s2s-client/tests -v ``` These checks cover frontend contracts and local session/queue/WebSocket fixtures. They do not validate deployed services, real microphone hardware, model output, or audio quality. Use a local browser/backend reproduction for those paths before deployment.