github/spec-kit · error · PresetValidationError

Invalid strategy ' ' for script: scripts only support

Error message

Invalid strategy '{strategy}' for script: scripts only support {sorted(VALID_SCRIPT_STRATEGIES)}

What it means

A stricter, script-specific strategy check: entries with `type: script` only support {"replace", "wrap"}. Prepending or appending to an executable script does not make sense semantically, so it is rejected even though those strategies are valid for other template types.

Solutions

  1. Set the script template's strategy to "replace" or "wrap".
  2. If you actually need prepend/append behavior, the entry is not a script — reconsider its type or restructure so the script itself composes content.
  3. Omit strategy for scripts wanting full overwrite — default is "replace".

Example fix

# before
  - name: setup
    type: script
    file: scripts/setup.sh
    strategy: append

# after
    strategy: "replace"
Defensive patterns

Strategy: validation

Validate before calling

SCRIPT_OK = {"replace", "wrap"}
for t in yaml.safe_load(open("preset.yml")).get("templates", []):
    if t.get("type") == "script":
        s = str(t.get("strategy", "replace")).lower()
        assert s in SCRIPT_OK, f"script {t.get('name')}: strategy must be replace or wrap, got {s!r}"

Type guard

def script_strategy_ok(template: dict) -> bool:
    return str(template.get("strategy", "replace")).lower() in {"replace", "wrap"}

Try / catch

try:
    manager.install_from_directory(src, version)
except PresetValidationError as e:
    if "for script" in str(e):
        # script templates only accept replace/wrap; fix and retry
        ...

Prevention

When it happens

Trigger: preset.yml contains a template with `type: script` and `strategy: prepend` or `strategy: append`. The general strategy check (VALID_PRESET_STRATEGIES) passes first, then this script-specific check fails.

Common situations: Copying a command template entry (which supports prepend/append for content merging) and changing only its type to script, or assuming one uniform strategy vocabulary across all template types.

Related errors


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

Appendix: source

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

            # 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:
                if not re.match(r'^[a-z0-9-]+$', tmpl["name"]):
                    raise PresetValidationError(
                        f"Invalid template name '{tmpl['name']}': "
                        "must be lowercase alphanumeric with hyphens only"
                    )

View on GitHub (pinned to bf88c9f9a8)