can1357/oh-my-pi · error · TypeError

Composer shape "${id}" must have a label

Error message

Composer shape "${id}" must have a label

What it means

After validating the id, registerComposerShape requires definition.label to contain non-whitespace content (label.trim().length > 0). An empty or whitespace-only label throws a TypeError naming the shape id.

Source

Thrown at packages/coding-agent/src/extensibility/extensions/loader.ts:246

			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" },
	): void {
		this.runtime.sendMessage(message, options);
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Provide a non-empty label string for the composer shape.
  2. Fall back to a default label when config/i18n data is missing: label ?? 'My Shape'.
  3. Trim and check the label before calling registerComposerShape and surface a clearer extension-level error.

Example fix

// before
api.registerComposerShape({ style: { id: 'cards' }, label: config.label ?? '', ... });
// after
api.registerComposerShape({ style: { id: 'cards' }, label: config.label ?? 'Cards', ... });
Defensive patterns

Strategy: validation

Validate before calling

const label = rawLabel?.trim();
if (!label) throw new Error('composer shape label required');
api.registerComposerShape({ style: { id }, label, ... });

Type guard

function hasLabel(def: ComposerShapeDefinition): boolean {
  return typeof def.label === 'string' && def.label.trim().length > 0;
}

Try / catch

try {
  api.registerComposerShape(def);
} catch (err) {
  if (err instanceof TypeError && err.message.includes('must have a label')) {
    def.label = def.style.id; // fallback label
    api.registerComposerShape(def);
  } else throw err;
}

Prevention

When it happens

Trigger: An extension registers a composer shape with label: '' or label: ' ', or a label sourced from missing i18n/config data.

Common situations: Localization file missing the label key so the lookup returns empty string; config value not yet loaded when the extension registers; developer passed a placeholder empty label while scaffolding.

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/f59b1d5348d2117a. Report an issue: GitHub.