Help us improve
Share bugs, ideas, or general feedback.
From copilotkit
Debugging guide for CopilotKit v2: error code catalogs, SSE event tracing, web inspector setup, and onError wiring for provider and chat.
npx claudepluginhub copilotkit/copilotkit --plugin copilotkitHow this skill is triggered — by the user, by Claude, or both
Slash command
/copilotkit:debug-and-troubleshootThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Debug in layers: server debug first, then client debug, then the web
Diagnoses CopilotKit issues including runtime connectivity failures, unresponsive agents, streaming errors, tool execution problems, transcription failures, version mismatches, and AG-UI event tracing.
Guides systematic debugging for broken features, errors, failed deployments, or tests: reproduce bugs, gather git/logs diagnostics, read errors, diagnose root causes before fixes.
Teaches reading error messages, troubleshooting, and fixing common frontend JavaScript, backend server, and database bugs. Activates on errors or user debugging requests.
Share bugs, ideas, or general feedback.
Debug in layers: server debug first, then client debug, then the web
inspector. Handle errors in onError using CopilotKitCoreErrorCode
string literals (snake_case).
// app/routes/api.copilotkit.$.tsx
import { CopilotRuntime } from "@copilotkit/runtime/v2";
const runtime = new CopilotRuntime({
agents,
debug:
process.env.NODE_ENV !== "production"
? { events: true, lifecycle: true, verbose: true }
: false,
});
import { CopilotKitProvider, CopilotChat } from "@copilotkit/react-core/v2";
<CopilotKitProvider
runtimeUrl="/api/copilotkit"
showDevConsole="auto"
debug={process.env.NODE_ENV !== "production"}
onError={({ error, code, context }) => {
// central telemetry; keep UI toasts on the chat:
telemetry.captureException(error, { tags: { code }, extra: context });
}}
>
<CopilotChat
agentId="default"
onError={({ code }) => {
if (code === "agent_thread_locked") {
toast({ title: "Agent busy — try again in a moment" });
}
}}
/>
</CopilotKitProvider>;
<CopilotKitProvider
runtimeUrl="/api/copilotkit"
showDevConsole="auto"
debug={{ events: true, lifecycle: true }}
/>
showDevConsole="auto" mounts the inspector in development. It lazy-loads
@copilotkit/web-inspector via @lit-labs/react — zero cost in prod.
runtime_info_fetch_failedChecks in order:
runtimeUrl starts with a leading / or is a full origin./info is reachable:curl -i http://localhost:3000/api/copilotkit/info
createCopilotRuntimeHandler({ cors: true }) or proxy-level CORS).credentials="include" on the provider AND CORS
configured to allow credentials.agent_not_foundagents: { default: ... } key matches the client
<CopilotChat agentId="default"> / useAgent({ agentId }) string./info JSON lists the expected agent names.agent_thread_lockedDouble-submit or concurrent run. Handle it:
<CopilotKitProvider
runtimeUrl="/api/copilotkit"
onError={({ code }) => {
if (code === "agent_thread_locked") {
toast.warning("Agent is busy — please wait");
}
}}
/>
Dev tools Network tab on the /run SSE stream shows each event frame.
Server debug produces Pino logs with every event emitted. If the event
is missing in the Pino logs, the agent factory isn't yielding it — fix
the agent. If it's in the Pino logs but not the browser, check the SSE
connection isn't being buffered (proxies, compression).
Safe aliases — mechanical find/replace, behavior unchanged:
| Deprecated / alias | Canonical |
|---|---|
publicLicenseKey (alias) | publicApiKey (canonical; resolution is publicApiKey ?? publicLicenseKey) |
agents__unsafe_dev_only | (no prod alias — use runtimeUrl or publicApiKey) |
selfManagedAgents | (no prod alias — same as above) |
createCopilotEndpoint* aliases (still accepted) | createCopilotRuntimeHandler |
createCopilotExpressHandler / createCopilotHonoHandler | mount createCopilotRuntimeHandler in the framework's native route |
beforeRequestMiddleware | hooks.onRequest (both run pre-dispatch, before route resolution) |
afterRequestMiddleware | hooks.onResponse (both run post-handler, on the outbound Response) |
Renamed props (breaking — semantics changed, not just names):
| Old prop | New prop | Why it's breaking |
|---|---|---|
imageUploadsEnabled | attachments={{ enabled: true }} | attachments covers the broader file/paste/drag surface, not just image uploads; the shape is an object, not a boolean. |
Wrong:
onError: ({ code }) => {
if (code === "API_NOT_FOUND") {
/* never matches */
}
};
Correct:
onError: ({ code }) => {
if (code === "runtime_info_fetch_failed") {
/* matches */
}
};
v2 codes are snake_case on CopilotKitCoreErrorCode
(runtime_info_fetch_failed, agent_run_failed, agent_thread_locked,
tool_handler_failed, …). v1 SCREAMING_SNAKE values never match v2.
Source: packages/core/src/core/core.ts:71-105
Wrong:
// turning on client debug and puzzling over missing events
<CopilotKitProvider debug={{ events: true, verbose: true }} />
Correct:
// turn on server debug FIRST
new CopilotRuntime({
agents,
debug: { events: true, lifecycle: true, verbose: true },
});
Server drops events too; Pino server logs are more reliable as the first trace point. If the event is in Pino but not the browser, then look at the SSE stream in the Network tab.
Source: docs/snippets/shared/troubleshooting/debug-mode.mdx:62-69,129-141
Wrong:
<CopilotKitProvider runtimeUrl="/api/copilotkit" />
// no onError — double-submit shows a scary error banner
Correct:
<CopilotKitProvider
runtimeUrl="/api/copilotkit"
onError={({ code }) => {
if (code === "agent_thread_locked") {
return toast({ title: "Agent busy — try again in a moment" });
}
}}
/>
agent_thread_locked is the common concurrent-run error — treat it as
a user-facing busy signal, not a crash.
Source: packages/core/src/core/core.ts:81-97
Wrong:
<CopilotKitProvider debug={true} />
// expecting every event payload in the console — only gets summaries
Correct:
<CopilotKitProvider debug={{ events: true, lifecycle: true, verbose: true }} />
Boolean true enables events + lifecycle summaries but keeps
verbose: false. Verbose is opt-in because it may log PII.
Source: docs/snippets/shared/troubleshooting/debug-mode.mdx:85-93
Wrong:
<CopilotKitProvider onError={toast}>
<CopilotChat onError={toast} />
</CopilotKitProvider>
Correct:
<CopilotKitProvider onError={telemetry}>
<CopilotChat onError={toast} />
</CopilotKitProvider>
Chat onError fires IN ADDITION TO provider onError — double toasts
if both trigger UI. Canonical split: telemetry on provider, UI on chat.
Source: docs/snippets/shared/troubleshooting/error-debugging.mdx:56-70