Troubleshooting#

This page covers common problems with a chat embedded through the Neo SDK, and the error codes that the proxy returns. The error code is the error field in the JSON body of the response, which the network panel of the browser shows. For setting up the proxy and the page, see the Embedding Neo Chat in Your Own App or Site page.

The Chat on Your Page#

Answers Arrive All at Once Instead of Streaming#

A reverse proxy between the browser and the chat proxy buffers the response. For nginx, set proxy_buffering off; and a generous proxy_read_timeout on the location of the chat. Some CDNs need buffering turned off for that path as well.

The Chat Is Invisible#

<neo-chat> has no height. Give it display: block and a height. A floating chat needs no size.

Dialogs Are Cut Off at the Edge of a Container#

In the React setup, an ancestor of NeoSdkProvider with a transform, filter, contain, or clipping overflow limits the dialogs of the chat to its own box. Move the provider outside that ancestor.

CORS Errors in the Browser Console#

The page and the proxy are on different origins, and the origin of the page is missing from corsOrigins. Add the origin of the page to ALLOWED_ORIGINS, which the templates use for both corsOrigins and the anonymous origin check.

Signed-In Readers Get 401 across Origins#

The proxy relies on a session cookie, which does not reach a proxy on another origin. Switch the page to auth: 'token' with a getToken function, or serve the proxy from the origin of your page.

The Recent Files List Is Empty#

This is expected. Because every reader shares the service account, the proxy refuses to list files, and it refuses uploads too, unless it sets allowUploads: true.

Math Symbols Render as Plain Letters#

Math in answers uses the serif font of your page instead of the KaTeX math fonts. Blackboard, calligraphic, and fraktur letters then render as plain ones, and stretchy delimiters keep text-size parentheses. If your answers need them, load katex.min.css from KaTeX, fonts included, in your own page.

A Document with a Reserved ID Does Not Open#

The widgets and sdk.items refuse an item ID that matches a name that the Squirro API reserves, before any request is sent. <neo-item-detail> reports it as a neo-item-detail-error. For the reserved names, see the docs/chat.md file in the package.

The PDF Viewer#

The PDF Viewer Never Finishes Loading#

The viewer files come from your proxy at <baseUrl>/assets/nutrient/. Check the network panel of the browser for assets/nutrient/ requests:

  • If the page and the proxy are on different origins, and the origin of the page is missing from corsOrigins, the browser blocks the downloads of the viewer. The viewer never reports that, so the SDK waits 30 seconds, and then shows an error that names the assets URL and CORS.

  • If the route responds with 404, because the proxy has serveNutrientAssets: false or @nutrient-sdk/viewer is missing from its installation, the error names the viewer script instead.

  • A Content Security Policy without worker-src blob: or 'wasm-unsafe-eval' stops the viewer as well, and the console names the directive.

The reader can still open the file from Open original in the document header.

A Blocked Request to nutrient-powered.io in the Console#

The PDF viewer reports usage to Nutrient after loading, at least when it runs without a license key. Your Content Security Policy or your network blocked that report. The viewer is not affected.

Proxy Startup#

The Proxy Exits at Startup, or Answers 503 proxy_not_ready#

The startup check failed, and the error names why:

  • Assistant memory is on

    Memory is on for the service account in the project that the error names. Turn it off, or set ALLOW_SHARED_MEMORY=1 if every reader may share one memory.

  • Scheduled tasks not allowed

    ALLOW_SHARED_AGENT_TOOLS must list tasks, because a project cannot turn off scheduled tasks. Set it to tasks,filesystem, or to tasks if file storage is turned off in every project that the proxy serves.

  • File storage not turned off

    ALLOW_SHARED_AGENT_TOOLS does not list filesystem, and the error names capability.filesystem.enabled. Pin it to false in the project manifest and in every agent that sets it, or add filesystem if every reader may share the file storage of the service account.

  • The refresh token was refused

    The error says that exchanging refreshToken failed. Your Squirro instance refused SQUIRRO_REFRESH_TOKEN because it is mistyped, expired, revoked, or replaced by a renewed token, because the role of the service account changed, or because SQUIRRO_API_URL points at the wrong place.

  • It could not check

    The proxy could not read whether memory is on, or the catalog of the project. If the error names a 403, the service account is not a member of that project, or the ID in PROJECT_ID is wrong. Otherwise, your Squirro instance did not respond, or responded with an error. The proxy does not serve requests until the check succeeds.

For how to turn off memory, and how to share or turn off file storage, see the Prepare the Service Account and the Project section of the Set Up the Proxy page.

A proxy whose check failed responds with 503 proxy_not_ready, and the first request 10 seconds later runs the check again. A proxy that keeps running therefore recovers once your Squirro instance responds or the setting is fixed. When the check fails at startup, the templates exit instead, and rely on the process manager that restarts them. An error that says “the proxy has stopped serving” comes from the check that the proxy repeats while it serves: memory or file storage was turned on while the proxy ran, without being allowed.

Requests Refused or Failing#

403 origin_not_allowed or origin_required#

The origin of the page is not in ALLOWED_ORIGINS, or the browser sent no origin at all. Check the exact scheme, host, and port. A page served with Referrer-Policy: no-referrer sends no origin on same-origin requests. Relax the policy on pages with the chat, or use bearerSession.

403 cross_site_request#

A request that changes something came from a page whose origin the proxy does not recognize as its own. Usually, a reverse proxy in front sends a different Host than the browser used. The nginx default is Host: $proxy_host, with no X-Forwarded-Host. Add proxy_set_header Host $host;, or list the origin of the page in ALLOWED_ORIGINS with the templates, or in trustedOrigins otherwise. Alternatively, send X-Forwarded-Host and set trustProxy: true on createChatProxy, which the templates do not set. TRUST_PROXY is a different setting, for the rate limit. A page on another site is refused on purpose.

403 host_not_allowed#

The Host of the request is not in the allowedHosts of the proxy. Add the host name that the browser uses.

403 agent_not_allowed#

The reader picked an assistant outside allowedAgentIds. If readers may use it, add its ID to allowedAgentIds.

403 uploads_disabled or 400 file_ids_not_allowed#

Uploads are off, because every reader shares the service account. Set allowUploads: true on the proxy only if every reader may see the files of every other reader.

400 query_value_not_allowed or 403 purge_not_allowed#

A request carried a query parameter value that the SDK never sends. The proxy forwards only the parameters that the requests of the SDK carry. A 403 purge_not_allowed appears only when your own isAllowedPath opens conversation deletion, and a request asks for a hard delete while every reader shares the service account.

404 for a Project That Exists#

The project is not in PROJECT_ID. Add its ID to PROJECT_ID.

404 for Every Document#

With the Workspaces feature turned on, the proxy needs the tenant of the workspace that holds the project, and SQUIRRO_TENANT is not set. The proxy reports it once to onWarning. Set SQUIRRO_TENANT. For which tenant to set, see the Which Tenant to Set section of the Set Up the Proxy page.

413 payload_too_large#

A request body other than an upload is over 1 MB. The chat never sends one that large, so check what sends the request.

504 upstream_timeout or 502 upstream_unavailable#

The proxy could not reach your Squirro instance, or the instance stopped responding:

  • A 504 means that connecting took longer than 10 seconds, or that the instance sent no response within two minutes.

  • A 502 means that the connection to the instance failed before a response started, for example because the instance refused it, or that the proxy could not exchange its refresh token, for example because it was revoked.

  • A response that has already started and then goes silent is cut off instead: after five minutes for a streamed answer, and after 30 seconds for any other response.

The onError of the proxy names the step that stalled: dns, tcp, tls, queue, response, idle, upload, or download. queue means that the connection pool of the proxy itself is full: raise it with the HTTP agent options agent or streamAgent, or add replicas. Check SQUIRRO_API_URL, and that the server of the proxy can reach the instance. For dns timeouts on a busy server, set UV_THREADPOOL_SIZE=16.

An upload whose body arrives too slowly gets 408 request_timeout instead, with the phase upload. That happens when the instance is ready for more data and the upload stalls, or when the whole upload does not arrive within upstreamUploadTimeoutMs, 10 minutes by default. The connection of the reader was too slow, not the instance. An instance that stops reading for two minutes gets 504 with the phase response.

An upload cut off with a bare 408, without a JSON body, or with a reset, and with nothing in onError, ran into the Node.js server.requestTimeout, which is 5 minutes by default. The templates raise it to 11 minutes. Every other request body is read whole, up to 1 MB, before the proxy contacts the instance.

The Express Proxy Serves Nothing#

The handler is mounted under a path, for example with app.use('/api/chat', ...). Express then strips that prefix from the request URL, and the handler finds nothing to serve. Mount the handler without a path, as the proxy-express.mjs template does.

Rate Limits#

The limits in this section apply to the anonymous(...) mode of the proxy.

429 rate_limited#

The address of the reader used up its rate limit, or its stricter budget for questions and searches, expensiveRateLimit, 20 per minute by default. The Retry-After header of the response says when to try again. If many readers share one address, for example behind a corporate gateway, raise max. If your proxy sits behind a load balancer, set trustProxyHeader: true in anonymous(...), or TRUST_PROXY with the templates, so that each reader is counted separately.

The files of the PDF viewer and artifact downloads count against a separate budget, assetRateLimit, 600 requests per minute by default. Raise it for readers behind one gateway who open many PDF documents.

Every Reader Hits 429 at the Same Time#

The proxy counts every reader as the same address. Usually, it sits behind a reverse proxy that does not set X-Forwarded-For, or that the proxy does not trust. Add proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;, and check TRUST_PROXY. For a reverse proxy on the same machine, the onWarning of the proxy says which of the two it is, once. For a reverse proxy on another host, nothing warns, so set TRUST_PROXY explicitly.

429 too_many_streams#

One address already has four expensive requests in flight, such as answers streaming, uploads, or searches (maxConcurrentStreams). Readers behind one gateway share the limit, so raise it for those deployments.

429 too_many_requests#

One address already has 16 requests in flight (maxConcurrentRequests), such as documents that it opened and has not finished reading. Readers behind one gateway share the limit, so raise it for those deployments.

429 too_many_downloads#

One address already has four documents or artifact downloads in flight (maxConcurrentDownloads), such as PDF documents that it opened and has not finished reading. Readers behind one gateway share the limit, so raise it for those deployments.

Downloads#

Download Does Nothing: 403 invalid_signature or signature_expired#

Download in the chat fetches the signed link of the artifact, and saves the response. When the proxy refuses it, nothing is saved, and the chat reports it through callbacks.onNotify, which the SDK forwards to onWarning by default, so it shows in the console. The 403 and its error show on the request in the network panel of the browser.

  • signature_expired means that the download link has expired. Click Download again for a fresh link.

  • invalid_signature for every download usually means that the tenant option of the proxy names a tenant other than the one that holds the project. For which tenant to set, see the Which Tenant to Set section of the Set Up the Proxy page.

Download Does Nothing: 429 rate_limited#

As with the 403 in the previous section, nothing is saved, and the 429 shows in the network panel of the browser. With bearerSession or your own authorize, signed artifact downloads have a budget of their own, signedDownloadRateLimit, of 30 per minute for each conversation by default. A reader who downloads more than that from one conversation in a minute waits for Retry-After.

If you set key: 'client' in signedDownloadRateLimit, the budget applies to each reader address instead. Behind a reverse proxy, also set trustedHops to the number of reverse proxies in front, or every reader counts as the address of the reverse proxy and they all share one budget. trustProxy does not help here, because it only changes the cross-site check.