can1357/oh-my-pi · error · Error

Capability "${def.id}" is already defined

Error message

Capability "${def.id}" is already defined

What it means

defineCapability() stores capability definitions in a module-level registry keyed by id. Calling it twice with the same id would silently overwrite the first definition, so the registry throws. This protects the unique-id invariant that registerProvider and loadCapability rely on.

Source

Thrown at packages/coding-agent/src/capability/index.ts:54

/** Provider display metadata (shared across capabilities) */
const providerMeta = new Map<string, { displayName: string; description: string }>();

/** Disabled providers (by ID) */
const disabledProviders = new Set<string>();

/** Settings manager for persistence (if set) */
let settings: Settings | null = null;

// =============================================================================
// Registration API
// =============================================================================

/**
 * Define a new capability.
 */
export function defineCapability<T>(def: Omit<Capability<T>, "providers">): Capability<T> {
	if (capabilities.has(def.id)) {
		throw new Error(`Capability "${def.id}" is already defined`);
	}
	const capability: Capability<T> = { ...def, providers: [] };
	capabilities.set(def.id, capability as Capability<unknown>);
	return capability;
}

/**
 * Register a provider for a capability.
 */
export function registerProvider<T>(capabilityId: string, provider: Provider<T>): void {
	const capability = capabilities.get(capabilityId);
	if (!capability) {
		throw new Error(`Unknown capability: "${capabilityId}". Define it first with defineCapability().`);
	}

	// Store provider metadata (for cross-capability display)
	if (!providerMeta.has(provider.id)) {
		providerMeta.set(provider.id, {

View on GitHub (pinned to 9690622007)

Solutions

  1. Rename your capability id to a unique namespaced value (e.g. 'myplugin.myCapability')
  2. Check whether the module containing the defineCapability call is loaded twice (duplicate bundling, double registration) and fix the loading path
  3. If re-defining is intentional, wrap the call in try/catch or look up the existing capability first

Example fix

// before
defineCapability({ id: "hooks", ... });
// after
defineCapability({ id: "myplugin.hooks", ... });
Defensive patterns

Strategy: try-catch

Validate before calling

// idempotent define
const CAPABILITY_ID = "myplugin.hooks";
if (!isCapabilityDefined(CAPABILITY_ID)) {
  defineCapability({ id: CAPABILITY_ID, displayName: "My Hooks" /* ... */ });
}

Try / catch

let capability: Capability<Hooks>;
try {
  capability = defineCapability({ id: "myplugin.hooks", displayName: "My Hooks" });
} catch (err) {
  if (err instanceof Error && err.message.includes('already defined')) {
    capability = getCapability<Hooks>("myplugin.hooks");
  } else throw err;
}

Prevention

When it happens

Trigger: Calling defineCapability({ id: "existing-id", ... }) when a capability with that id was already defined — typically module-level definitions evaluated twice, two modules defining the same id, or module re-import/duplicate-bundle evaluation.

Common situations: A bundler duplicates a module so its top-level defineCapability call runs twice; a plugin copies another capability's definition without changing the id; an extension is loaded twice under different names.

Related errors


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