can1357/oh-my-pi · error · TypeError
Composer shape id must be a non-empty trimmed string
Error message
Composer shape id must be a non-empty trimmed string
What it means
registerComposerShape validates that definition.style.id is a non-empty, trimmed string before registering. An empty id, whitespace-only id, or an id with leading/trailing whitespace is rejected with a TypeError.
Source
Thrown at packages/coding-agent/src/extensibility/extensions/loader.ts:243
): void {
this.extension.flags.set(name, { name, extensionPath: this.extension.path, ...options });
if (options.default !== undefined) {
this.runtime.flagValues.set(name, options.default);
}
}
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" },View on GitHub (pinned to 9690622007)
Solutions
- Trim the id before calling: id: definition.style.id.trim().
- Ensure the id is a non-empty constant string in the extension source.
- Validate the config-driven id at load time before constructing the ComposerShapeDefinition.
Example fix
// before
api.registerComposerShape({ style: { id: `prefix-${name} ` }, ... });
// after
const id = `prefix-${name}`.trim();
if (id) api.registerComposerShape({ style: { id }, ... }); Defensive patterns
Strategy: validation
Validate before calling
const id = rawId?.trim() ?? '';
if (id.length === 0) throw new Error('composer shape id required');
api.registerComposerShape({ style: { id }, ... }); Type guard
function isValidShapeId(id: unknown): id is string {
return typeof id === 'string' && id.length > 0 && id === id.trim();
} Try / catch
try {
api.registerComposerShape(def);
} catch (err) {
if (err instanceof TypeError && err.message.includes('Composer shape id')) {
logger.error('invalid composer shape id', { id: def?.style?.id });
} else throw err;
} Prevention
- Always .trim() ids built from templates or config.
- Define shape ids as constants, never inline interpolated strings.
- Validate config-driven ids at extension startup.
- Watch for stray whitespace when concatenating prefixes/names.
When it happens
Trigger: An extension calls registerComposerShape({ style: { id: '' } }), id: ' myshape ' (leading/trailing spaces), or id from an unparsed/interpolated value that ends up empty.
Common situations: Composer shape id built by template concatenation picking up stray spaces; a config-driven id read from JSON/YAML that is blank; interpolation producing empty string when a variable is unset.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Composer shape "${id}" must have a label
- Cannot replace built-in composer shape "${id}"
- Invalid service tier "${String(tier)}" for family "${String(
- Unsupported language '{value}'. Supported: {}
- Unable to infer language from file extension: {}. Specify `l
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/9969e0a19ba05eea.
Report an issue: GitHub.