Troubleshooting
Common issues and solutions when working with GuideKit.
Setup Issues
Missing API Key
Symptom: GuideKitError: LLM_API_KEY_MISSING on initialization.
Fix (production): Use proxy mode with server-side keys. Run npx guidekit init to scaffold lib/guidekit-routes.ts and /api/guidekit/* routes, then set GUIDEKIT_SECRET and LLM_API_KEY in .env.local. See Getting Started.
Fix (dev only): You can pass a client key for quick prototyping — never ship this to production:
<GuideKitProvider
llm={{ provider: 'gemini', apiKey: process.env.NEXT_PUBLIC_GEMINI_KEY! }}
>For production, use the Server SDK to keep API keys server-side.
Wrong Provider Configuration
Symptom: GuideKitError: LLM_PROVIDER_INVALID or unexpected model responses.
Fix: Check that the type field matches your API key provider:
// Gemini
llm={{ provider: 'gemini', apiKey: '...' }}
// OpenAI
llm={{ provider: 'openai', apiKey: '...' }}
// Anthropic
llm={{ provider: 'anthropic', apiKey: '...' }}SDK Not Initializing
Symptom: isReady stays false, no errors visible.
Fix:
- Ensure
<GuideKitProvider>wraps your app at the top level. - Check browser console for errors — enable debug mode for detailed logs:
<GuideKitProvider
options={{ debug: true }}
proxy={{ llm: '/api/guidekit/llm', health: '/api/guidekit/health' }}
tokenEndpoint="/api/guidekit/token"
>- Verify you are not rendering on the server without SSR guards.
Voice Mode Issues
Microphone Permission Denied
Symptom: GuideKitError: PERMISSION_DENIED when starting voice.
Fix:
- Ensure your site is served over HTTPS (microphone access requires a secure context).
- Check browser settings to confirm the microphone permission is not blocked for your domain.
- Call
startListening()in response to a user gesture (click, tap) — browsers block mic access without user interaction.
Browser Compatibility
Voice features require:
- Chrome 89+ / Edge 89+ — full support (Web Speech API + AudioContext)
- Safari 15.4+ — supported with
webkitAudioContextfallback - Firefox 100+ — Web Speech API support varies; Deepgram STT recommended
If voice is unavailable, use the checkCapabilities() method to detect support:
const caps = await core.checkCapabilities();
if (caps.mic.status === 'unavailable') {
// Hide voice UI
}Echo Detection / Feedback Loop
Symptom: The assistant hears its own TTS output and responds to it.
Fix: GuideKit includes built-in echo detection (60% word overlap within a 3-second window). If you experience echo issues:
- Use headphones during testing.
- Ensure TTS playback volume is not excessively high.
- The half-duplex state machine prevents listening while speaking — verify your state transitions are correct.
Bundle Size Issues
Bundle Larger Than Expected
Symptom: Production bundle exceeds expected size limits.
Fix:
- Use subpath imports to enable tree-shaking:
// Preferred — only imports what you need
import { GuideKitProvider } from '@guidekit/react';
import { createGuideKit } from '@guidekit/core';
// Avoid — may pull in unnecessary code
import * as GuideKit from '@guidekit/core';-
If you do not use voice features, avoid importing from
@guidekit/vad. -
Verify tree-shaking is working with your bundler. All GuideKit packages set
sideEffects: false. -
Run the size check to see current bundle sizes:
pnpm size:checkExpected gzip sizes: core 65KB, react 8KB, server 2KB, vanilla 92KB, cli 5KB.
Rendering Subpath Not Tree-Shaken
If you only use the core SDK without visual guidance, import from the base path:
import { createGuideKit } from '@guidekit/core';Visual/rendering exports live under @guidekit/core/rendering to keep the vanilla bundle smaller.
Production / Proxy Failures
Session expired or server restarted (401)
Symptom: LLM proxy returns 401 with “Session expired or server restarted — request a new token”.
Cause: Provider keys live in the server SessionStore, keyed by sessionId. After a deploy/restart (in-memory store) or TTL expiry, the JWT may still decode but keys are gone.
Fix:
- Ensure the client refreshes tokens (GuideKit refreshes at ~80% TTL automatically).
- For multi-instance/serverless, use
RedisSessionStore. - See Session recovery below.
Rate limit exceeded (429)
Symptom: Proxy returns 429 with Retry-After.
Fix: Back off and retry after the header value. Tune rateLimit.maxRequests on createGuideKitHandler — see Server SDK rate limiting.
Origin not allowed (403)
Symptom: Proxy returns 403 with “Origin not allowed”.
Fix: Ensure allowedOrigins on createSessionToken includes your app’s origin (scheme + host + port). Browser fetch from the app must send a matching Origin header.
Permission denied (403)
Symptom: STT/TTS/LLM/site-search proxy returns 403 with Permission “llm” not granted (or stt / tts / site:read).
Fix: Mint tokens with the required permissions array, e.g. ['stt', 'tts', 'llm', 'site:read'] (default).
Recoverable vs non-recoverable errors
Every GuideKitError includes recoverable:
recoverable | Meaning | Examples |
|---|---|---|
true | Safe to retry or continue | SEND_IN_FLIGHT, INPUT_TOO_LONG, rate limits, network blips |
false | Fix configuration or auth | Missing LLM config, invalid provider, fatal init errors |
Client pattern:
core.bus.on('error', (err) => {
if (err.recoverable) {
// show toast, allow retry
return;
}
// log to monitoring, show support message
});Use Observability (DevTools Telemetry tab or core.getTelemetrySpans()) to see which pipeline stage failed before the error surfaced.
Session recovery (client-side)
When the LLM proxy returns 401, the SDK should fetch a new token and retry. Contract coverage: e2e/contract/session-recovery.spec.ts.
If recovery loops:
- Confirm
/api/guidekit/tokenreturns 200 andLLM_API_KEYis set server-side. - Confirm all app instances share the same
SessionStore(Redis in production). - Enable
options={{ debug: true }}and watchauth:token-refreshedevents.
Error Codes
GuideKit uses structured error codes for all failures. Each GuideKitError includes:
code— a machine-readable error codemessage— a human-readable descriptionrecoverable— whether the error can be retriedsuggestion— a recommended fix
See the full Error Codes Reference for a complete list.
Debug Mode
Enable debug mode for verbose console output:
<GuideKitProvider
options={{ debug: true }}
proxy={{ llm: '/api/guidekit/llm', health: '/api/guidekit/health' }}
tokenEndpoint="/api/guidekit/token"
>This logs:
- EventBus events and handler execution
- LLM request/response cycles and tool calls
- DOM scanning and section detection
- Voice state machine transitions
- Resource manager lifecycle events
For programmatic access, subscribe to the error event on the EventBus:
const core = createGuideKit({ debug: true, provider: { ... } });
core.bus.on('error', (err) => {
// Send to your error tracking service
myErrorTracker.capture(err);
});