Troubleshooting
BetaLast updated Oct 5, 2026
Most problems come down to three things: the map, the adapter, or the voice setup. Start with the first checks, then find your symptom below.
First checks
- Log every decision with
onAction={(action) => console.log(action)}. Arefusedaction has areasonand adetailthat usually tell you exactly what’s wrong. - Pass
voice.onDiagnosticandvoice.onErrorand watch the console while you tap the orb. - Test your map without voice: remove the key and the
voiceprop, and type into the panel. The local mode uses the same map and validation chain. - Drive a turn from code with
ref.current.sendText("take me to billing")and inspect theAgentActionit returns.
The orb only opens a chat panel
- A malformed key doesn’t break the page: Usher logs a console error and runs text-only. Check the console, and check that the key starts with
pk_test_orpk_live_. - The key is undefined, so Usher is in local text-only mode (the panel status reads “Text mode”). Check that
VITE_VOQAL_KEYorNEXT_PUBLIC_VOQAL_KEYis set in.env.local, then restart the dev server. Vite andnext devread that file only at startup. - In a deployed Next.js app,
NEXT_PUBLIC_values are inlined at build time. Set the variable in the environment and rebuild.
The orb doesn't appear
- It’s fixed to the bottom-right at z-index 40. Check whether one of your overlays covers it.
- In Next.js, make sure it renders from a client component. See Render it in the browser only.
- Make sure
<Usher>is mounted on the page you’re looking at. Mount it once in your signed-in layout, not inside one route.
Voice won't start
- origin_not_allowed (403): your key is locked to certain domains, and this page’s origin isn’t one of them. Ask Voqal to add the exact origin (scheme, host, and port), or to remove the restriction.
http://localhostandhttp://127.0.0.1work on any port. - rate_limited (429): too many sessions for the key this minute. The panel says “Too many requests right now — please try again in a moment.”, and the status line reads “Too many requests — try again in a moment”. Wait and try again; if it happens in normal use, ask Voqal to raise your key’s limit.
- invalid_key: the key is unknown or disabled, or it doesn’t start with
pk_live_orpk_test_. - It stops listening after a while: the microphone pauses after 30 seconds without recognised speech. Tap the orb to resume, or change
idleTimeout.pauseMicAfterMs. - Permission denied:
onErrorgets aNotAllowedError. Reset the site’s microphone permission in the browser. Chat still works through Aa. - Not HTTPS: browsers only allow the microphone on HTTPS or localhost.
- Blocked socket: a CSP without the right
connect-srchost, or withoutblob:inscript-src, stops the session or the microphone. See CSP.
It talks but nothing moves
- It may have offered rather than navigated. That’s the design for “how do I” questions. Say “yes”, or say “take me to…”.
- You may already be on that page. Usher never navigates to, or offers, the current page (on a dynamic page, the same record): nothing moves, no card appears, and
onActiongetsrefusedwithalready_here. - It was asked to do something rather than go somewhere (“book it for me”), and none of your tools fits. The assistant says it can’t and explains how to do it on the page. That’s deliberate: add a tool for that action.
- Look for a
refusedaction.not_in_allowlistmeans the id isn’t in the browser’s map. navigation_failedmeans your adapter’snavigatethrew. Thedetailholds your router’s error.- You added a page to
destinationsduring a live session. An open session keeps the map it started with. End it and start a new one. See When prop changes apply.
It stopped replying
- Every message gets a reply or an honest fallback. “Thinking…” stays until the reply starts. If the model says nothing, even after Usher nudges it, or nothing arrives within 20 seconds, the panel shows “Sorry — I didn’t catch that. Could you say a bit more?” and records
turn:silentorfallback:shown. - If you see that line often, send Voqal the trace id from the console. The trace findings show which reliability events fired.
- If the panel shows a connection or rate-limit line instead, see the entries on disconnects and voice errors.
- A greeting or “ok” gets a reply, never a navigation. That’s deliberate.
It picks the wrong page
- Give each important page a specific
titleand a one-linesemanticDescription. That’s what the voice model reads. - Remove generic one-word aliases shared by sibling pages. Add the phrases users actually say.
- Discovered-only routes get titles from their path (
/settings/teambecomes “team”). Add a manifest entry for anything users ask for by name. - The local ranker only matches ASCII letters and digits. Aliases in other scripts are ignored there, though the voice model still reads titles and descriptions in any language.
It keeps asking which one
- For dynamic pages: a
param_unresolvedrefusal means the id couldn’t be found. Passcontext.selectedEntityon detail pages, or addresolveEntity. - For similar pages: two destinations score close together. Make their aliases and descriptions clearly different.
It doesn't use my knowledge base
- Look for a
console.warnsaying entries didn’t fit. A live session carries at most 8,000 characters of whole entries, in the order you list them. Put the most important entries first, and keep each one to a sentence or two. - Knowledge loaded after the session started reaches the next session, not the open one.
- In local text mode, an entry has to share at least 30% of the question’s words. Add
triggerPhrasesfor how users ask.
It thinks I'm on another page
- Your adapter’s
currentLocation()is stale. In Next.js, keep the location in a ref and passsubscribe, as in the App Router example. - Without
onLocationChangeevents, a live session isn’t told when the user moves by hand.
The conversation resets or disconnects
- A changed
assistantIdororganizationIdstarts a new conversation. That’s deliberate. - Signing out with
runtime().logout()ends the session and clears the panel, as intended. - × on the orb and End in the chat panel end the session and clear the transcript. A tap on the orb only pauses voice, and – in the panel only minimizes it.
- Voice stays off after I minimize the panel. That’s deliberate: – always pauses voice and never turns it back on by itself. Tap the orb, or the panel’s voice button (“Turn on voice”), to talk again. While voice is off, the panel says “Voice replies off”: replies show as text and nothing is spoken.
- “Reconnecting…” means the connection dropped (a network blip, or the voice provider’s periodic connection limit) and Usher is bringing it back. Messages typed meanwhile are held and sent automatically, and the new connection gets a recap of the recent chat. Nothing to do.
- “Connection lost — send a message to reconnect” means reconnecting failed, and
UsherSessionLostErrorreachesonError. A message still waiting shows “Please send that again.” The next message starts a new session that continues from a recap. Repeated losses point to the network or a Content Security Policy block; check the console. - “Paused — send a message to continue” is not an error: the idle timeout or the maximum length ended the session. The next message continues where it left off.
- A session that ends on its own after a while (“Paused”) is the idle timeout (5 minutes without activity) or the maximum session length (10 minutes). Both are adjustable with
idleTimeout. See Idle timeout & session limits. - A full page reload starts a new session. The conversation lives in memory for this beta.
Next.js
- “useRouter only works in Client Components”: add
"use client"to the file that renders<Usher>.
Getting help
During the beta, email team@voqal.ai with the trace id from the browser console of the affected page load (the line that starts [usher] trace tr_), the package versions, your router, and the AgentAction from onAction. The trace id lets us find that session’s diagnostics. Leave out tokens and user data. See Support & diagnostics.
