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

  1. Rename the shape to a unique, namespaced id (e.g. 'myext-cards') instead of a builtin id.
  2. List the builtin style ids and pick a non-conflicting name.
  3. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/868a41c2005e6d46. Report an issue: GitHub.