github/copilot-sdk · error

Factory phase title " " is declared more than once

Error message

Factory phase title "${phase.title}" is declared more than once

What it means

validatePhases enforces that each factory phase title is unique, tracked via a Set during validation. Duplicate titles would make phases ambiguous for display and step association, so defineFactory rejects the definition. Note that comparison is on the exact title string, not the trimmed value.

Solutions

  1. Rename the duplicate phase so every title in meta.phases is unique.
  2. Deduplicate or suffix generated titles programmatically before calling defineFactory.
  3. Merge the duplicate phases into a single phase if they represent the same stage.
  4. Validate titles for uniqueness in the upstream data source.

Example fix

// before
defineFactory({ name: 'build', phases: [{ title: 'Compile' }, { title: 'Compile' }] });
// after
defineFactory({ name: 'build', phases: [{ title: 'Compile' }, { title: 'Link' }] });
Defensive patterns

Strategy: validation

Validate before calling

const seen = new Set();
for (const p of phases) {
  if (seen.has(p.title)) throw new TypeError(`Duplicate phase title: "${p.title}"`);
  seen.add(p.title);
}

Type guard

function hasUniqueTitles(phases) { return new Set(phases.map(p => p.title)).size === phases.length; }

Try / catch

try {
  defineFactory(meta);
} catch (e) {
  const m = /phase title "(.+)" is declared more than once/.exec(e.message);
  if (m) console.error(`Rename duplicate phase: ${m[1]}`);
  throw e;
}

Prevention

When it happens

Trigger: Calling defineFactory whose meta.phases array contains two phases with the identical title string. Common when phases are generated from data that repeats a label, or when copy-pasting phase blocks without renaming the title.

Common situations: Generated phase lists from configs or spreadsheets with repeated labels; copy-pasted phase definitions; merging phase arrays from multiple sources that use the same names; programmatic phase creation with a template title not updated per phase.

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 github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/6cfbe7f5817abc11. Report an issue: GitHub.

Appendix: source

Thrown at nodejs/src/factory.ts:471

            limits.maxAiCredits <= 0 ||
            !Number.isSafeInteger(maxNanoAiu) ||
            maxNanoAiu < 1
        ) {
            throw new Error(
                'Factory limit "maxAiCredits" must be a positive, finite number that rounds to a safe positive integer nano-AIU ceiling'
            );
        }
    }
}

function validatePhases(meta: FactoryMeta): void {
    const titles = new Set<string>();
    for (const phase of meta.phases) {
        if (phase.title.trim().length === 0) {
            throw new Error("Factory phase titles must not be empty");
        }
        if (titles.has(phase.title)) {
            throw new Error(`Factory phase title "${phase.title}" is declared more than once`);
        }
        titles.add(phase.title);
    }
}

/**
 * Defines an extension-authored factory and returns an opaque registration handle.
 *
 * @experimental Part of the experimental Agent Factories surface and may
 * change or be removed in future SDK or CLI releases.
 */
export function defineFactory<
    TArgs extends JsonValue = JsonValue,
    TResult extends JsonValue | void = JsonValue | void,
>(definition: FactoryDefinition<TArgs, TResult>): FactoryHandle<TArgs, TResult> {
    // Snapshot before validating so post-registration mutation of the caller's
    // object cannot slip past the authoring-boundary checks.
    const meta = deepFreeze(structuredClone(definition.meta));

View on GitHub (pinned to cd8cf15dc3)