can1357/oh-my-pi · error · Error
Composer style "${id}" is already registered
Error message
Composer style "${id}" is already registered What it means
registerComposerStyle() rejects duplicate registrations: if an extension style with the same id is already present in the extension map, it throws instead of overwriting. Built-in ids are rejected separately (a different error).
Source
Thrown at packages/tui/src/components/composer/registry.ts:48
*
* Extensions registered before `filledSurface` existed received undecorated
* row text, so an omitted flag remains filled for extension-owned ids.
*/
export function isFilledComposerStyle(style: ComposerStyle): boolean {
return style.filledSurface ?? !isBuiltinComposerStyle(style.id);
}
/**
* Register one extension-owned composer style for this process.
*
* Built-in ids and duplicate extension ids are rejected. The returned disposer
* removes only this registration.
*/
export function registerComposerStyle(style: ComposerStyle): () => void {
const id = style.id.trim();
if (id.length === 0 || id !== style.id) throw new TypeError("Composer style id must be a non-empty trimmed string");
if (isBuiltinComposerStyle(id)) throw new Error(`Cannot replace built-in composer style "${id}"`);
if (extensionComposerStyles.has(id)) throw new Error(`Composer style "${id}" is already registered`);
extensionComposerStyles.set(id, style);
return () => {
if (extensionComposerStyles.get(id) === style) extensionComposerStyles.delete(id);
};
}
/** Style object for a composer shape; unknown ids fall back to `box`. */
export function getComposerStyle(id: EditorBorderStyle): ComposerStyle {
return extensionComposerStyles.get(id) ?? BUILTIN_COMPOSER_STYLES[id] ?? boxComposerStyle;
}
View on GitHub (pinned to 9690622007)
Solutions
- Call the disposer returned by the first registerComposerStyle call before re-registering
- Guard registration so it runs only once per id
- Use a unique id per extension/feature
Example fix
// before registerComposerStyle(style) registerComposerStyle(style) // throws // after const dispose = registerComposerStyle(style) // ... dispose() // then re-register safely registerComposerStyle(style)
Defensive patterns
Strategy: try-catch
Validate before calling
// track your own registrations to avoid duplicates
const registered = new Set<string>();
function safeRegister(style: ComposerStyle) {
if (registered.has(style.id)) return;
registered.add(style.id);
disposers.set(style.id, registerComposerStyle(style));
} Try / catch
const disposers = new Map<string, () => void>();
function registerOnce(style: ComposerStyle) {
disposers.get(style.id)?.();
disposers.set(style.id, registerComposerStyle(style));
} Prevention
- Store the disposer returned by registerComposerStyle and call it before re-registering
- Make extension registration idempotent (guard against hot-reload double-runs)
- Give each extension a unique id per feature
When it happens
Trigger: Calling registerComposerStyle twice with the same extension style id without disposing the first registration.
Common situations: Hot-reload re-running an extension's registration code; a plugin loaded twice; forgetting to call the disposer returned by a previous registration before re-registering.
Related errors
- Composer style id must be a non-empty trimmed string
- Cannot replace built-in composer style "${id}"
- No messages to continue from
- Cannot continue from message role: assistant
- Gemini Files API cannot delete a handle from another provide
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a65fb59714065ec1.
Report an issue: GitHub.