Documentation menu

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:

  1. Is the path covered? In Next.js, requests outside config.matcher never reach the gateway at all. Widen the matcher to include the URL.
  2. 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, and null means HTML. (On the Node adapter, sources see originalUrl, not a router-rewritten url.) Test the resolver directly with the failing pathname.
  3. 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 bare Signature pair) deliberately gets HTML — the gateway never guesses a format nobody asked for.
  4. Is it a crawler? Googlebot and bingbot get HTML always, even with Accept: text/markdown — that is the cloaking guardrail working, not a bug.
  5. 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 Authorization header? The sink sends Authorization: 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.

proxy.ts — composing with existing middleware
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 matcher must 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 null for 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: Accept can 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 contains text/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.accept from 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.