can1357/oh-my-pi · error · Error
Cannot replace built-in composer shape "${id}"
Error message
Cannot replace built-in composer shape "${id}" What it means
registerComposerShape refuses to let an extension overwrite a built-in composer style. If the given id passes isBuiltinComposerStyle(id), an Error is thrown rather than silently replacing the core shape.
Source
Thrown at packages/coding-agent/src/extensibility/extensions/loader.ts:249
registerMessageRenderer<T>(customType: string, renderer: MessageRenderer<T>): void {
this.extension.messageRenderers.set(customType, renderer as MessageRenderer);
}
registerAssistantThinkingRenderer(renderer: AssistantThinkingRenderer): void {
this.extension.assistantThinkingRenderers.push(renderer);
}
registerComposerShape(definition: ComposerShapeDefinition): void {
const id = definition.style.id;
if (id.length === 0 || id !== id.trim()) {
throw new TypeError("Composer shape id must be a non-empty trimmed string");
}
if (definition.label.trim().length === 0) {
throw new TypeError(`Composer shape "${id}" must have a label`);
}
if (isBuiltinComposerStyle(id)) {
throw new Error(`Cannot replace built-in composer shape "${id}"`);
}
this.extension.composerShapes.set(id, definition);
}
getFlag(name: string): boolean | string | undefined {
if (!this.extension.flags.has(name)) return undefined;
return this.runtime.flagValues.get(name);
}
sendMessage<T = unknown>(
message: CustomMessagePayload<T>,
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
): void {
this.runtime.sendMessage(message, options);
}
sendUserMessage(
content: string | (TextContent | ImageContent)[],View on GitHub (pinned to 9690622007)
Solutions
- Rename the shape to a unique, namespaced id (e.g. 'myext-cards') instead of a builtin id.
- List the builtin style ids and pick a non-conflicting name.
- If the goal is visual similarity, subclass/copy the builtin definition under a new id.
Example fix
// before
api.registerComposerShape({ style: { id: 'default' }, ... });
// after
api.registerComposerShape({ style: { id: 'myext-cards' }, ... }); Defensive patterns
Strategy: validation
Validate before calling
if (isBuiltinComposerStyle(id)) throw new Error(`rename shape: '${id}' is built-in`);
api.registerComposerShape({ style: { id }, ... }); Type guard
function isCustomShapeId(id: string): boolean {
return !isBuiltinComposerStyle(id);
} Try / catch
try {
api.registerComposerShape(def);
} catch (err) {
if (err.message.includes('Cannot replace built-in composer shape')) {
logger.warn('composer shape id conflicts with builtin, pick a namespaced id', { id: def.style.id });
} else throw err;
} Prevention
- Namespace custom ids: 'myext-myshape'.
- Never copy a builtin id as a starting point without renaming.
- Keep a list of builtin ids in your team docs and check new ids against it.
- Generate dynamic ids with an extension-specific prefix.
When it happens
Trigger: An extension registers a composer shape whose style.id collides with a built-in style name (e.g. 'default' or another core style id).
Common situations: Developer names a custom shape the same as a builtin assuming override semantics; copying a builtin id as a starting point and forgetting to rename it; dynamic id generation colliding with builtin names.
Related errors
- Composer shape id must be a non-empty trimmed string
- Composer shape "${id}" must have a label
- Invalid alias "omp". Refusing to shadow the base omp command
- Invalid service tier "${String(tier)}" for family "${String(
- Unsupported language '{value}'. Supported: {}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/868a41c2005e6d46.
Report an issue: GitHub.