github/copilot-sdk · error

Duplicate factory name

Error message

Duplicate factory name "${definition.meta.name}". Factory names must be unique within a joinSession call.

What it means

During joinSession, each factory handle is resolved to a definition and stored in this.factories keyed by meta.name. Registering two factories with the same name within one joinSession call is ambiguous (which definition should serve the name?), so it throws before mutating further.

Solutions

  1. Give each factory a unique meta.name before joining.
  2. Deduplicate the factories array before passing it to joinSession.
  3. If two entries are truly the same factory, remove one from the list.

Example fix

// before
joinSession({ factories: [loggerFactory, loggerFactory] });
// after
joinSession({ factories: [...new Map(defs.map((d) => [getFactoryDefinition(d).meta.name, d])).values()] });
Defensive patterns

Strategy: validation

Validate before calling

const names = factories.map((f) => getFactoryDefinition(f).meta.name);
const dupes = names.filter((n, i) => names.indexOf(n) !== i);
if (dupes.length) throw new Error(`Duplicate factory names: ${dupes.join(', ')}`);

Try / catch

try { await joinSession({ factories }); } catch (e) { if (String(e.message).includes('Duplicate factory name')) console.error('Deduplicate factories by meta.name before joining'); else throw e; }

Prevention

When it happens

Trigger: Passing a factories array to joinSession containing two handles whose definitions share the same meta.name — duplicates from spreading lists, or a factory accidentally included twice.

Common situations: Combining [ ...baseFactories, ...baseFactories ] or shared defaults arrays; copying a factory and changing only its behavior, not meta.name; re-exporting the same definition under two variable names.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/3f555edde0644dff. Report an issue: GitHub.

Appendix: source

Thrown at nodejs/src/session.ts:1447

    }

    /**
     * Registers factory closures and reverse-RPC handlers for this session.
     *
     * @param factories - Factory handles declared by the joining extension.
     * @internal Called by the SDK when an extension joins a session.
     */
    registerFactories(factories?: FactoryHandle[]): void {
        this.factories.clear();
        if (!factories || factories.length === 0) {
            delete this.clientSessionApis.factory;
            return;
        }

        for (const handle of factories) {
            const definition = getFactoryDefinition(handle);
            if (this.factories.has(definition.meta.name)) {
                throw new Error(
                    `Duplicate factory name "${definition.meta.name}". Factory names must be unique within a joinSession call.`
                );
            }
            this.factories.set(definition.meta.name, definition);
        }

        const self = this;
        this.clientSessionApis.factory = {
            async execute(params) {
                const definition = self.factories.get(params.name);
                if (!definition) {
                    const message = `No factory registered with name "${params.name}"`;
                    throw new ResponseError(ErrorCodes.InvalidParams, message, {
                        code: "factory_not_found",
                        name: params.name,
                    });
                }

View on GitHub (pinned to cd8cf15dc3)