Skip to content

[Bug]: specify init --here --force does not reapply installed presets, leaving preset registry out of sync with disk #3990

Description

@takaya0

Bug Description

Re-running specify init --here --force regenerates integration skill files from the bundled core but does not reapply installed presets. The preset registry (.specify/presets/.registry) and specify preset list still report the preset as installed/enabled, while the materialized skill file on disk has silently reverted to the pristine core version. The registry and the on-disk state diverge with no warning.

specify integration upgrade <key> --force behaves differently (it preserves the preset-composed output), so init --here --force is asymmetric with the upgrade path.

Steps to Reproduce

  1. Create an empty project and initialize spec-kit:

    mkdir demo && cd demo && git init -q && git commit -q --allow-empty -m init
    specify init --here --integration claude --script sh --ignore-agent-tools --force
    sha256sum .claude/skills/speckit-specify/SKILL.md   # record core hash (A)
  2. Create a minimal dev preset that wraps speckit.specify:

    presets-src/demo-wrap/preset.yml

    schema_version: "1.0"
    preset:
      id: "demo-wrap"
      name: "Demo Wrap"
      version: "1.0.0"
      description: "Wraps speckit.specify with a preamble"
      author: "demo"
      license: "MIT"
    requires:
      speckit_version: ">=0.1.0"
    provides:
      templates:
        - type: "command"
          name: "speckit.specify"
          file: "commands/speckit.specify.md"
          description: "Wrap speckit.specify with a preamble"
          strategy: "wrap"

    presets-src/demo-wrap/commands/speckit.specify.md

    ---
    description: "Wrapped speckit.specify"
    strategy: wrap
    ---
    
    <!-- demo preamble -->
    
    {CORE_TEMPLATE}
  3. Apply it and confirm composition works:

    specify preset add --dev ./presets-src/demo-wrap
    sha256sum .claude/skills/speckit-specify/SKILL.md   # composed hash (B), contains preamble + full core body
  4. Re-run init:

    specify init --here --integration claude --script sh --ignore-agent-tools --force
    sha256sum .claude/skills/speckit-specify/SKILL.md   # back to core hash (A) — wrap is gone
    specify preset list                                  # still shows demo-wrap as installed (Templates: 1)
    cat .specify/presets/.registry                       # still lists demo-wrap as enabled

Reproduced twice from scratch with identical results.

Expected Behavior

After init --here --force regenerates the integration files, installed presets should be re-composed on top of the fresh core (or, at minimum, the CLI should warn that installed presets are no longer applied / mark them accordingly), so that preset list never reports a preset as active while the files on disk are pure core.

Actual Behavior

The materialized skill file reverts to the core version while the preset registry still reports the preset as installed and enabled. There is no warning. Recovery attempts:

  • specify preset add --dev ... again → fails with Error: Preset 'demo-wrap' is already installed. Use 'specify preset remove ...' first.
  • specify preset disableenable → no re-composition (output itself says "Previously registered commands/skills remain active until preset removal")
  • specify preset set-priority → no re-composition

The only working recovery is the two-step specify preset remove demo-wrapspecify preset add --dev ./presets-src/demo-wrap.

Interestingly, specify integration upgrade claude --force --script sh re-applies while keeping the preset composition intact (verified twice), so only the init path loses it.

Specify CLI Version

0.15.2

AI Agent

Claude Code

Operating System

macOS 15 (Apple Silicon, Darwin 25.5.0)

Python Version

3.14.6

Additional Context

Possibly related but distinct: #3849 fixed integration upgrade --force overwriting extension/preset-derived SKILL.md content (PR #3853); that fix touches the upgrade path only, and in 0.15.2 the upgrade path indeed preserves the composition — it is the init --here --force path that reverts it while leaving the registry stale.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions