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

  1. Trim the id before calling: id: definition.style.id.trim().
  2. Ensure the id is a non-empty constant string in the extension source.
  3. 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

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


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