Troubleshooting
Troubleshooting
Common integration issues and their solutions, organized by symptom.
CORS errors
Symptom: Browser console shows:
Access to fetch at 'https://knowledge-api.theeducationalequalityinstitute.org/api/chat'from origin 'https://cockpit.theeducationalequalityinstitute.org' has been blockedby CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.Cause: The host platform’s origin is not in the RAG Worker’s ALLOWED_ORIGINS.
Fix:
- Open
teei-knowledge/rag-worker/wrangler.toml. - Add the origin to the
ALLOWED_ORIGINSvariable:
[vars]ALLOWED_ORIGINS = "https://theeducationalequalityinstitute.org,https://cockpit.theeducationalequalityinstitute.org,https://wbp.theeducationalequalityinstitute.org,https://admin.theeducationalequalityinstitute.org,https://docs.theeducationalequalityinstitute.org,https://your-new-origin.example.com"- Redeploy the Worker:
cd teei-knowledge/rag-workerwrangler deployFor local development: The [env.dev] section uses ALLOWED_ORIGINS = "http://localhost:*", which matches all localhost ports. If you’re getting CORS errors locally, verify you’re running wrangler dev --env dev (not the production Worker).
Verification:
curl -I -X OPTIONS https://knowledge-api.theeducationalequalityinstitute.org/api/chat \ -H "Origin: https://cockpit.theeducationalequalityinstitute.org" \ -H "Access-Control-Request-Method: POST"# → Access-Control-Allow-Origin: https://cockpit.theeducationalequalityinstitute.orgWidget not appearing
Symptom: Page loads without errors, but the chat FAB is not visible anywhere.
Cause A — Component not rendered: The ChatProvider/ChatWidget is conditionally excluded.
Check: Open the browser’s element inspector and search for .teei-chat-widget. If the element is absent from the DOM, the Astro template condition (isExcluded, session &&, etc.) is evaluating to false and the widget is not being rendered.
Fix: Add a temporary console.log in the layout frontmatter to verify the condition:
---console.log('Widget excluded?', isExcluded, 'Path:', path);---Cause B — CSS not imported: The widget renders but is invisible because the stylesheet is missing.
Check: Search the page’s <head> for a stylesheet with chat-widget or teei-chat in the URL. If absent, the import is missing.
Fix: Ensure the styles are imported in the layout:
---import '@teei/chat-widget/styles';---Or in a global CSS file:
@import '@teei/chat-widget/styles';Cause C — Hydration not complete: client:idle waits for the browser to be idle before hydrating. On a very active page (many timers, animations), the widget may take a few seconds to appear.
Check: Open DevTools → Performance. Look for the teei-chat-widget DOM node. If it appears after 3–5 seconds, hydration is just delayed.
Fix: If the widget needs to appear immediately, change to client:load. This adds a small blocking cost but ensures instant render.
Cause D — Z-index stack buried: The widget renders but is hidden behind another element.
Check: In DevTools, find .teei-chat-fab and inspect its computed z-index. If another element has a higher z-index and covers the same screen position, the FAB is invisible.
Fix: Increase the widget’s z-index:
/* In platform CSS file */.teei-chat-widget { --teei-chat-z: 99999;}Auth not working / wrong role
Symptom: Widget appears but returns answers appropriate for a lower role than the user has. For example, a CSR manager sees only public content.
Cause A — Role prop not passed correctly: The userRole prop is hardcoded or the role mapping is wrong.
Check: In the browser DevTools → Network tab → filter for requests to /api/chat. Click a request and inspect the request body. Verify user_role has the expected value:
{ "query": "...", "platform": "csr-cockpit", "user_role": "manager", ← check this "language": "en"}Fix: Trace the userRole value from the layout frontmatter through to the ChatProvider prop. If it’s wrong, fix the role mapping in the layout.
Cause B — Role map has wrong keys: The KNOWLEDGE_ROLE_MAP in the layout doesn’t handle all possible role values.
Fix: Add a fallback to the map:
const knowledgeRole = KNOWLEDGE_ROLE_MAP[managerRole] ?? 'employee'; // safe fallbackLog the value during development to confirm:
---console.log('[Chat] managerRole:', managerRole, '→ knowledgeRole:', knowledgeRole);---Cause C — Content not ingested for the role: The user has the correct role but the vector store has no content tagged for that role. The widget returns “I don’t have information about that.”
Fix: Run the ingestion scripts and verify content is tagged correctly:
wrangler d1 execute teei-knowledge-db \ --command "SELECT role, COUNT(*) as count FROM chunks GROUP BY role"Expected output shows rows for public, volunteer, employee, staff, manager, admin.
No answers returned
Symptom: Widget opens, accepts a question, shows loading spinner, then returns “I don’t have information about that” or an empty response for most questions.
Cause A — Vectorize has no data: The index is empty because ingestion has not been run.
Check:
wrangler vectorize list teei-knowledge-indexIf the index exists but returns 0 vectors, run the ingestion scripts.
Cause B — Content tagged for wrong platform: Content exists in Vectorize but is tagged platform: admin and the query uses platform: website. The platform filter excludes it.
Check: Inspect D1 chunk tags:
wrangler d1 execute teei-knowledge-db \ --command "SELECT platform, role, COUNT(*) as count FROM chunks GROUP BY platform, role ORDER BY count DESC"Verify the platform and role values match what the widget sends.
Cause C — Vectorize returns 0 similarity matches: The query vector is not similar enough to any stored vectors. This happens when:
- The content is in English but the query is in Ukrainian (or vice versa) — bge-m3 is multilingual but similarity degrades across languages when only one language is indexed
- The query is very different in phrasing from the stored content (jargon vs plain language)
Fix: Ingest content in the same language as the user’s query. bge-m3 handles cross-lingual retrieval reasonably well, but same-language retrieval is significantly better.
Cause D — AI Gateway returns an error: The gateway is misconfigured or the Anthropic API key is missing.
Check:
curl -X POST http://localhost:8787/api/chat \ -H "Content-Type: application/json" \ -d '{"query": "test", "platform": "website", "user_role": "public", "language": "en"}'If the response includes "error": "...", check the Workers logs:
wrangler tail teei-knowledge-apiSlow responses
Symptom: Queries take 5–15 seconds before any streamed token appears.
Cause A — AI Gateway cache is cold: The first request for any given query generates a fresh embedding + vector search + Claude completion. Subsequent identical queries return from cache.
Expected first-response latency: 2–5 seconds (embedding + vector search + first Claude token).
Expected cached latency: < 500ms.
If first responses consistently exceed 10 seconds, the issue is elsewhere.
Cause B — AI Gateway caching is disabled: Check the AI Gateway configuration in the Cloudflare Dashboard (AI > AI Gateway > teei-knowledge > Settings). Cache must be enabled.
Cause C — Workers AI smart placement not active: The wrangler.toml includes [placement] mode = "smart" which routes the Worker to the Cloudflare datacenter nearest to the Workers AI inference cluster. Verify this is not overridden.
Cause D — Vectorize similarity search returning many low-quality results: If the vector store has many chunks with low similarity scores, the RAG Worker may be passing too much context to Claude, causing long completions.
Optimization: The RAG Worker queries Vectorize for the top-K results. If K is too large (e.g., 20 chunks), reduce it to 5–8 for faster responses with minimal quality loss.
Widget conflicts with other fixed-position elements
Symptom: The chat FAB overlaps with another UI element (annotation overlay, support widget, cookie banner, etc.).
Fix — Move the FAB:
/* In the platform's CSS file */.teei-chat-widget .teei-chat-fab { bottom: 88px; /* Push above a 64px-tall bottom bar */}.teei-chat-widget .teei-chat-panel { bottom: 88px;}Or use position="bottom-left" on ChatProvider to move the widget to the opposite corner.
Fix — Lower the z-index:
.teei-chat-widget { --teei-chat-z: 9990; /* Below annotation overlay at 9994 */}Note: lowering the z-index means modal overlays (DemoExitIntent at 9997, DemoContactDrawer at 9998) will cover the chat panel when open. This is usually acceptable behaviour.
TypeScript errors after installing the widget
Symptom: npx astro check or tsc reports errors from @teei/chat-widget.
Cause A — Package not built: The local package (file:../chat-widget) must be built before TypeScript can find the type declarations.
Fix:
cd teei-knowledge/chat-widgetpnpm build # Generates dist/index.d.ts
cd ../ # Back to platform projectpnpm install # Re-links the local packageCause B — React version mismatch: The widget is compiled for React 18/19. If the platform uses a different React version (unlikely but possible), type mismatches occur.
Check: grep '"react"' package.json in both the platform and the widget.
Cause C — client:idle directive type error in Astro: Some older Astro type setups require explicit JSX type configuration for React components used with Astro directives.
Fix: Ensure tsconfig.json includes:
{ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "react" }}Widget crashes after page navigation (view transitions)
Symptom: Widget panel works on first page load, but after navigating to another page via Astro view transitions, the widget becomes unresponsive or throws React errors.
Cause: The React island unmounts and remounts on each navigation, losing state.
Fix: Add transition:persist to preserve the widget across navigations:
<ChatProvider transition:persist client:idle apiUrl={...} platform={...} userRole={...}> <ChatWidget /></ChatProvider>transition:persist tells Astro’s view transition system to keep the DOM node in place rather than destroying and recreating it.