github/spec-kit · error · PresetValidationError

Invalid strategy ' ': must be one of

Error message

Invalid strategy '{strategy}': must be one of {sorted(VALID_PRESET_STRATEGIES)}

What it means

The template's 'strategy' string is not one of the supported merge strategies. Valid values are {"replace", "prepend", "append", "wrap"} (compared case-insensitively — the value is lowercased before the check, and the normalized value is written back into the entry). The error message lists the exact accepted set.

Solutions

  1. Change the template's strategy in preset.yml to one of: replace, prepend, append, wrap.
  2. If you wanted to fully overwrite the target file, use "replace" (the default — you can omit the key).
  3. Check the error message's sorted list; it reflects the exact VALID_PRESET_STRATEGIES set for your installed spec-kit version.

Example fix

# before
    strategy: merge

# after
    strategy: "replace"
Defensive patterns

Strategy: validation

Validate before calling

VALID = {"replace", "prepend", "append", "wrap"}
for t in yaml.safe_load(open("preset.yml")).get("templates", []):
    s = str(t.get("strategy", "replace")).lower()
    if s not in VALID:
        raise SystemExit(f"bad strategy {s!r}; allowed: {sorted(VALID)}")

Type guard

def is_valid_strategy(value) -> bool:
    return isinstance(value, str) and value.lower() in {"replace", "prepend", "append", "wrap"}

Try / catch

try:
    manager.install_from_directory(src, version)
except PresetValidationError as e:
    if "Invalid strategy" in str(e):
        print(e)  # message lists the exact allowed set for this spec-kit version

Prevention

When it happens

Trigger: Calling preset install (or PresetManifest validation) with a preset.yml template entry like `strategy: merge`, `strategy: overwrite`, or a typo such as `strategy: apend`. Any string outside {append, prepend, replace, wrap} after lowercasing triggers it.

Common situations: Porting a preset from another tool whose strategy vocabulary differs (merge/overwrite), typos, or docs from an older/newer spec-kit version that supports a different strategy set.

Related errors


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

Appendix: source

Thrown at src/specify_cli/presets/__init__.py:462

            if os.path.isabs(normalized) or normalized.startswith(".."):
                raise PresetValidationError(
                    f"Invalid template file path '{file_path}': "
                    "must be a relative path within the preset directory"
                )

            # Validate strategy field (optional, defaults to "replace")
            strategy = tmpl.get("strategy", "replace")
            if not isinstance(strategy, str):
                raise PresetValidationError(
                    f"Invalid strategy value: must be a string, "
                    f"got {type(strategy).__name__}"
                )
            strategy = strategy.lower()
            # Persist normalized value so downstream code sees lowercase
            if "strategy" in tmpl:
                tmpl["strategy"] = strategy
            if strategy not in VALID_PRESET_STRATEGIES:
                raise PresetValidationError(
                    f"Invalid strategy '{strategy}': "
                    f"must be one of {sorted(VALID_PRESET_STRATEGIES)}"
                )
            if tmpl["type"] == "script" and strategy not in VALID_SCRIPT_STRATEGIES:
                raise PresetValidationError(
                    f"Invalid strategy '{strategy}' for script: "
                    f"scripts only support {sorted(VALID_SCRIPT_STRATEGIES)}"
                )

            # Validate template name format
            if tmpl["type"] == "command":
                # Commands use dot notation (e.g. speckit.specify)
                if not re.match(r'^[a-z0-9.-]+$', tmpl["name"]):
                    raise PresetValidationError(
                        f"Invalid command name '{tmpl['name']}': "
                        "must be lowercase alphanumeric with hyphens and dots only"
                    )
            else:

View on GitHub (pinned to bf88c9f9a8)