github/spec-kit · error · ValidationError

Invalid {singular} entry '{name}': 'strategy' is not authora

Error message

Invalid {singular} entry '{name}': 'strategy' is not authorable for extension-provided artifacts, which always use 'replace' semantics

What it means

A provides.templates/scripts entry contains a 'strategy' key. 'strategy' (merge/replace layering semantics) is authorable only in presets; extension-provided artifacts ALWAYS use 'replace' semantics, so the key is rejected rather than silently ignored. This protects authors who copy a preset-style entry from getting a silently-dropped field — the error names the exact entry.

Source

Thrown at src/specify_cli/extensions/__init__.py:623

            if name in seen_names:
                raise ValidationError(
                    f"Duplicate {singular} name '{name}' in 'provides.{section}'"
                )
            seen_names.add(name)

            file_value = entry["file"]
            reason = relative_extension_path_violation(file_value)
            if reason:
                label = repr(file_value) if isinstance(file_value, str) else f"for {singular} '{name}'"
                raise ValidationError(f"Invalid {singular} 'file' {label}: {reason}")

            if "description" in entry and not isinstance(entry["description"], str):
                raise ValidationError(
                    f"Invalid {singular} description for '{name}': expected a string"
                )

            if "strategy" in entry:
                raise ValidationError(
                    f"Invalid {singular} entry '{name}': 'strategy' is not authorable for "
                    "extension-provided artifacts, which always use 'replace' semantics"
                )

            if section == "scripts" and "runtimes" in entry:
                runtimes = entry["runtimes"]
                if not isinstance(runtimes, list) or not all(
                    isinstance(r, str) for r in runtimes
                ):
                    raise ValidationError(
                        f"Invalid runtimes for script '{name}': expected a list of strings"
                    )
                invalid = sorted(set(runtimes) - VALID_SCRIPT_RUNTIMES)
                if invalid:
                    raise ValidationError(
                        f"Invalid runtimes {invalid} for script '{name}': "
                        f"must be one of {sorted(VALID_SCRIPT_RUNTIMES)}"
                    )

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Delete the 'strategy' key from the entry — extension artifacts always replace.
  2. If you need merge/layering behavior, express it via a preset rather than an extension-provided artifact.
  3. If you depended on merging, restructure so the extension template is complete on its own.

Example fix

// before
{ "name": "plan", "file": "templates/plan.md", "strategy": "merge" }
// after
{ "name": "plan", "file": "templates/plan.md" }
Defensive patterns

Strategy: validation

Validate before calling

for section in ("templates", "scripts"):
    for e in manifest.get("provides", {}).get(section, []):
        if isinstance(e, dict):
            assert "strategy" not in e, f"'strategy' is not allowed on extension {section}: {e.get('name')}"

Type guard

def has_no_strategy_key(entry: dict) -> bool:
    return "strategy" not in entry

Try / catch

try:
    ExtensionManifest.load(path)
except ValidationError as e:
    if "'strategy' is not authorable" in str(e):
        # delete the strategy key; extension artifacts always replace
        ...

Prevention

When it happens

Trigger: {"name": "plan", "file": "...", "strategy": "merge"} in provides.templates or provides.scripts. Raised by the 'strategy' in entry check in the artifact validator (contrast with presets/__init__.py where strategy is legal).

Common situations: Copy-pasting an entry from a preset file (where strategy is valid) into an extension manifest; assuming merge semantics exist for extension templates.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/cd22ebc420dbe484. Report an issue: GitHub.