github/copilot-sdk · error · CanvasError
canvas_action_no_handler
canvas_action_no_handler
Error message
No handler implemented for this canvas action
What it means
When invoke() finds the canvas but no handler is registered under actionHandlers for params.actionName, it throws a CanvasError with code canvas_action_no_handler. The session distinguishes "unknown canvas" from "canvas exists but doesn't implement the action".
Solutions
- Add the missing entry to the canvas's actionHandlers map.
- Fix the actionName spelling/casing to match a registered handler.
- Update client and server sides to the same version so declared and implemented actions agree.
Example fix
// before
registerCanvas({ canvasId: "c1", actionHandlers: {} });
await canvas.invoke({ canvasId: "c1", actionName: "export" });
// after
registerCanvas({ canvasId: "c1", actionHandlers: { export: async (p) => ({}) } }); Defensive patterns
Strategy: validation
Validate before calling
const handlers = getActionHandlers(canvasId);
if (!handlers?.[actionName]) throw new Error(`Canvas "${canvasId}" has no handler for "${actionName}"`); Try / catch
try { await canvas.invoke({ canvasId, actionName }); } catch (e) { if (e?.code === 'canvas_action_no_handler') console.error(`Implement handler "${actionName}" on canvas "${canvasId}"`); else throw e; } Prevention
- Define action names in a shared union type/action map used by both sides.
- Register a default/fallback handler for unsupported actions.
- Keep client action catalogs and canvas actionHandlers in sync via a single source of truth.
When it happens
Trigger: canvas.invoke({ canvasId, actionName }) where the canvas is registered but its actionHandlers map lacks actionName — typo'd action name, handler not implemented, or handler registered on a different canvas.
Common situations: Client shipped a new action before the canvas implementation registered it; version mismatch between client and session code; singular/plural or casing mismatch in action names.
Related errors
- No canvas registered with id
- workingDirectory is not supported with…
- env is not supported with RuntimeConnection.forInProcess()…
- No session found for sessionId
- Copilot request response used after RPC connection closed.
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/e2e85060e614b36a.
Report an issue: GitHub.
Appendix: source
Thrown at nodejs/src/session.ts:1417
}
},
async close(params) {
const canvas = self.canvases.get(params.canvasId);
if (!canvas) throw new Error(`No canvas registered with id "${params.canvasId}"`);
try {
if (canvas.onClose) {
await canvas.onClose(params);
}
} catch (error) {
throw toCanvasRpcError(error);
}
},
async invoke(params) {
const canvas = self.canvases.get(params.canvasId);
if (!canvas) throw new Error(`No canvas registered with id "${params.canvasId}"`);
const handler = canvas.actionHandlers.get(params.actionName);
if (!handler) {
throw new CanvasError(
"canvas_action_no_handler",
"No handler implemented for this canvas action"
);
}
try {
return (await handler(params)) as CanvasActionInvokeResult;
} catch (error) {
throw toCanvasRpcError(error);
}
},
};
}
/**
* Registers factory closures and reverse-RPC handlers for this session.
*
* @param factories - Factory handles declared by the joining extension.
* @internal Called by the SDK when an extension joins a session.View on GitHub (pinned to cd8cf15dc3)