Files
Ben Taylor 9df63beeef fix(runtime): name useSingleEndpoint when a single-route envelope hits a multi-route runtime (#6579)
Closes
[OSS-882](https://linear.app/copilotkit/issue/OSS-882/add-to-existing-journeys-reach-for-the-v1-compat-copilotkit-wrapper).

## The failure

The v1-compatible `<CopilotKit>` provider pins `useSingleEndpoint` to
`true`
([`copilotkit.tsx:108`](https://github.com/CopilotKit/CopilotKit/blob/main/packages/react-core/src/components/copilot-provider/copilotkit.tsx#L108)),
so its startup handshake POSTs `{ method: "info" }` at the base path. A
multi-route runtime — the default — matches no route for that path and
answered a bare `{"error":"Not found"}`, indistinguishable from a wrong
`basePath` or an unmounted handler.

Two independent onboarding validation runs hit this on their first
browser attempt and each had to guess the cause. Both were
*add-to-existing-app* journeys; the greenfield one reached for
`CopilotKitProvider` and never saw it.

## What changed

**The runtime says what happened.** `detectSingleRouteEnvelope`
recognises a POST whose JSON body carries a `method` the single-route
endpoint accepts, and the multi-route handler uses it at the one point
routing gives up. The 404 now carries a `code` and a message naming the
prop, plus a `logger.warn` so it lands in the dev-server terminal too.
Deliberately conservative — wrong verb, non-JSON, unknown method, or a
JSON POST that isn't an envelope all stay ordinary 404s, unchanged in
status and shape.

**The client stops discarding it.** All four `/info` callers (two in
`agent-registry.ts`, two in `agent.ts`) threw away the response body and
reported only the status, so a server-side diagnosis reached nobody.
They now go through `runtimeInfoError`, which folds a string `message`
from the body into the thrown error. Any future server-side diagnosis
reaches the developer for free.

**Docs.** Five pages paired a v2 multi-route handler with `<CopilotKit>`
and never mentioned the prop. Rather than a warning under a snippet that
is still wrong to copy, the snippets themselves now pass
`useSingleEndpoint={false}`, with a short callout linking to the
provider/handler mapping.

Two pages were deliberately left alone: `backend/runtime-endpoints.mdx`
already documents the pairing in full, and `cookbook/arcade.mdx` uses
`mode: "single-route"` on purpose and already explains it.
`backend/copilot-runtime.mdx` keeps its snippet as-is — it pairs with
the v1 endpoint, where the default is correct — and gains the caveat
only on its "switch to v2 handlers" note.

Option 3 in the issue (reconsidering the compat default) is **not** in
this PR.

## Testing

### Both halves connect, end to end

Real `createCopilotRuntimeHandler` + real `CopilotKitCore` configured
the way the v1 wrapper configures it — no mocks on either side:

```
code   : runtime_info_fetch_failed
message: Runtime info request failed with status 404: Received a single-route
         request envelope ({ method: "..." }) but this runtime is mounted in
         multi-route mode, so the request matched no route. If the frontend uses
         <CopilotKit> from @copilotkit/react-core/v2, pass useSingleEndpoint={false}
         — that provider defaults it to true. Otherwise mount the runtime with
         mode: "single-route" to serve this envelope.

PASS — the diagnostic reached the client
```

The server-side `logger.warn` fired in the same run, carrying `{ url,
path, method: 'info' }`.

### Unit tests

`packages/runtime` — `single-route-envelope-diagnostic.test.ts` (2
positive, 5 control):

```
 ✓ src/v2/runtime/__tests__/single-route-envelope-diagnostic.test.ts (7 tests) 26ms
      Tests  7 passed (7)
```

`packages/core` — `runtime-info-error-detail.test.ts` (2 positive, 5
control):

```
 ✓ src/__tests__/runtime-info-error-detail.test.ts (7 tests) 267ms
      Tests  7 passed (7)
```

### Mutation checks

Every new test was verified to fail when its mechanism is broken, in
both directions.

Detector forced to `return null` — the two positives die, the four
controls hold:

```
   × names useSingleEndpoint when the envelope is an info call
   × diagnoses every method the single-route envelope accepts
   ✓ leaves an ordinary unmatched route as a plain 404
   ✓ leaves a JSON POST that is not an envelope as a plain 404
   ✓ leaves an unrecognized method name as a plain 404
   ✓ does not diagnose a non-JSON POST
```

Detector forced to `return "info"` — the controls die instead, proving
they are not vacuous:

```
   ✓ names useSingleEndpoint when the envelope is an info call
   ✓ diagnoses every method the single-route envelope accepts
   × leaves an ordinary unmatched route as a plain 404
   × leaves a JSON POST that is not an envelope as a plain 404
   × leaves an unrecognized method name as a plain 404
   × does not diagnose a non-JSON POST
```

`runtimeInfoError` with the detail dropped, then with the `typeof
message === "string"` guard removed — each kills a different pair:

```
mutation: detail dropped              → 2 failed | 5 passed
mutation: accept any message field    → 2 failed | 5 passed
restored                              → 7 passed
```

### Full suites, builds, docs

| Check | Result |
|---|---|
| `packages/core` full suite | `Test Files 60 passed (60)` / `Tests 662
passed (662)` |
| `packages/runtime` full suite | `Test Files 142 passed (142)` / `Tests
2067 passed (2067)` |
| `packages/core` `tsc --noEmit` | clean |
| `packages/runtime` `tsdown` | `416 files` — build complete |
| MDX compile, 5 edited pages | all `OK` |
| pre-commit `nx run-many -t test,publint,attw` | passed across affected
projects |
| CI on `f94d1ab0` | 72 pass, 3 skipping, 0 fail |

Both suites are fully green. An earlier revision of this description
reported 6
runtime failures as pre-existing on `main`; they were not. They were
artifacts
of a worktree whose `node_modules` had been assembled by hand, and a
proper
`pnpm install` cleared all of them along with the inspector-metadata
failures
from a stale `@copilotkit/shared` dist. `main` is clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-20 10:37:40 -05:00
..

Shell Docs

showcase/shell-docs is the Next.js app that builds and serves docs.copilotkit.ai. Author CopilotKit product documentation here, not in the retired top-level docs/ app.

Run Locally

Shell-docs is a standalone npm-based app. You do not need a root install just to run the docs app locally.

cd showcase/scripts
npm install

cd ../shell-docs
npm install
npm run dev

The local dev server runs on port 3003.

http://localhost:3003

The shell-docs npm lifecycle generates registry, demo-content, setup-content, and search data before dev, build, and typecheck.

Validate Changes

Run these from showcase/shell-docs:

npm run build
npm run typecheck
npm run test

For repo-level CI parity, prefer Nx when a shell-docs target is available in the current checkout and root dependencies are installed. For normal shell-docs local development, the npm commands above are the canonical path.

Authoring Recipes

Showcase-Driven Framework Docs

Showcase-driven frameworks use docs_mode: generated. The docs are assembled from showcase registry/generated data, demos, source regions, shared/root MDX, snippets, and sparse framework overrides.

To update showcase-driven docs:

  1. Edit the showcase source of truth: manifests, demos, feature coverage, source regions, or registry inputs.
  2. Edit shared/root MDX only when the change applies across generated frameworks.
  3. Add sparse framework overrides only for real framework-specific differences.
  4. Do not hand-edit generated files under src/data/frameworks/.
  5. Validate routes, sidebar state, search results, snippets, and framework switching.

Authored Framework Docs

Authored frameworks use docs_mode: authored. The framework owns an MDX tree under src/content/docs/integrations/<docsFolder>/ with a meta.json sidebar.

To update authored docs:

  1. Check getDocsFolder() in src/lib/registry.ts; the URL slug and folder name may differ.
  2. Edit the MDX page under src/content/docs/integrations/<docsFolder>/.
  3. Update that folder's meta.json when adding, removing, or moving pages.
  4. Reuse shared snippets from src/content/snippets/ when content should stay consistent across frameworks.
  5. Validate the framework route, sidebar, search result, and any shared snippet render.

Reference Docs

Edit API reference pages under src/content/reference/.

The v2 reference does not use meta.json; navigation is generated by walking the tree and reading each page's title and description frontmatter. Only the legacy reference/v1/ tree uses meta.json.

Snippets

Reusable snippets live under src/content/snippets/. Snippets may be rendered by root docs, authored framework pages, and showcase-driven framework pages, so keep them general unless the path is intentionally framework-specific.

Frontend Applicability

Frontend routes use page-level applicability metadata, independent from where the content is authored. A page can be authored MDX, showcase-generated content, mirrored protocol docs, or reference content and still be universal across frontends.

Use the frontend field in page frontmatter or meta.json when a root doc should appear in non-React frontend docs:

  • universal — render the same page under /<frontend>/....
  • frontend-variant — render only when a matching page exists under src/content/docs/frontends/<frontend>/....
  • hide — omit the page from frontend-scoped docs.

Do not use "showcase-driven" as a proxy for frontend availability. Showcase derivation is an authoring/source detail; frontend applicability controls routing and sidebar inclusion.

AG-UI Mirrored Docs

AG-UI protocol docs are authored upstream in ag-ui-protocol/ag-ui. The src/content/ag-ui/ tree is a downstream mirror rendered on the CopilotKit docs host. Change AG-UI docs upstream first, then sync the mirror back into shell-docs.

The repository's top-level docs/ path is a symlink to showcase/shell-docs/ for contributor muscle memory. It is not a separate docs app. Do not recreate the old docs/content/docs/ tree; author CopilotKit docs in showcase/shell-docs/src/content/.