Troubleshooting
The failure modes people actually hit — what each one means, and the fix (including the ones that aren’t bugs).
An agent gets HTML instead of markdown
Pass-through is the gateway’s default answer — it serves markdown only when classification *and* source resolution both succeed. Check in this order:
- Is the path covered? In Next.js, requests outside
config.matchernever reach the gateway at all. Widen the matcher to include the URL. - Did your source return data for that exact URL? Resolvers get the request pathname verbatim — a trailing slash, locale prefix, or rewritten path that your lookup doesn’t key on means
null, andnullmeans HTML. (On the Node adapter, sources seeoriginalUrl, not a router-rewrittenurl.) Test the resolver directly with the failing pathname. - Did the requester actually classify as an agent? Send the header yourself:
curl -si -H 'Accept: text/markdown' <url>. If that returns markdown but your agent’s traffic doesn’t, the agent isn’t sending a negotiation signal. An unidentified agent with no markdown Accept (e.g. a bareSignaturepair) deliberately gets HTML — the gateway never guesses a format nobody asked for. - Is it a crawler? Googlebot and bingbot get HTML always, even with
Accept: text/markdown— that is the cloaking guardrail working, not a bug. - Did the source throw? A thrown source is contained as "no match" and the request passes through. The emitted event still fires — check your event feed or logs.
For a stable reproduction, mount the dedicated markdown route — it renders for any requester and returns 404 JSON when no source matches, which isolates source problems from classification problems.
401 from the events ingest
401 means an unknown, revoked, or missing API key. The sink treats any 4xx as non-retryable: the batch is dropped immediately and reported through onError — serving is never affected, but those events are gone.
- Rotated key? Rotation invalidates the predecessor immediately. Update the deployed env with the new key from Console → Settings.
- Missing
Authorizationheader? The sink sendsAuthorization: Bearer <apiKey>— check the key actually reaches your config (an unset env var interpolates to an empty string). - Watch
onError. It fires for every dropped event and batch; wiring it to your logger is the difference between noticing in minutes and noticing never.
Markdown is truncated
Output is capped at maxBytes (default 5120 bytes, UTF-8). Truncation is deliberate and bottom-up: description, attributes, and images go first, on whole lines only, with a fixed truncation note appended as the final line. The front-loaded facts and the variants table are never truncated — and if the facts alone exceed the budget, they are emitted anyway.
If agents need the full description, raise the budget in your config: maxBytes: 8192 (trymumm runs 8KB). Keep it modest — the small, front-loaded response *is* the product.
Vercel / Next middleware matcher conflicts
Next.js allows one proxy.ts (or middleware.ts) per app. If you already have one — Supabase session refresh, auth, redirects — don’t add a second; compose in the existing file: gateway first, your logic as the fallthrough.
const gateway = createGatewayProxy(gatewayConfig)
export default async function proxy(req: Request) {
return (await gateway(req)) ?? updateSession(req) // your existing middleware
}
// Union the matchers: your paths + the gateway's source paths.
export const config = { matcher: ['/products/:path*', '/policies/:path*', '/dashboard/:path*'] }- The
matchermust be the union of your existing middleware’s paths and the gateway’s source paths — the narrower of the two silently stops running otherwise. - The gateway returns
nullfor everything it doesn’t serve, so your middleware still runs on every matched request that isn’t agent markdown. - Keep static assets (
/_next/, images) out of the matcher; classifying them is wasted work.
How Vary: Accept interacts with CDN caching
Markdown responses carry Vary: Accept because the same URL serves HTML to browsers and markdown to agents — caches must key on the Accept header to stay correct. Two failure shapes when they don’t:
- A CDN that ignores
Vary: Acceptcan cache the markdown response and serve it to browsers (or cache HTML and serve it to agents). If your CDN can’t vary on Accept, exclude the gateway’s matched paths from edge caching, or configure a cache key that includes whether the Accept header containstext/markdown. - A CDN that normalizes/strips Accept before your origin prevents agents from negotiating at all — every request looks browserish and passes through. Check what Accept value actually reaches your server (log
request.acceptfrom the emitted events).
The dedicated markdown route sidesteps both: it is its own URL with a single representation, so any cache handles it correctly — a good permalink to hand to agent platforms.