Skip to content

[Bug]: wrap template composition can loop forever when core content contains {CORE_TEMPLATE} #4385

Description

@mforce

Bug Description

The Bash template resolver's wrap strategy can loop indefinitely when the resolved base content contains the literal {CORE_TEMPLATE} token.

The current loop replaces one token in layer_content with content, then scans the modified layer_content again. If content contains {CORE_TEMPLATE}, every iteration reintroduces a token and the resolver never terminates.

This is present on current main at commit 31e6b84f701b108a848847a8bc9ea86a1e2890b7 in scripts/bash/common.sh.

Steps to Reproduce

  1. Initialize a project with Bash scripts.

  2. Add this preset registry:

    .specify/presets/.registry

    {
      "presets": {
        "a-wrap": { "enabled": true, "priority": 1 },
        "b-base": { "enabled": true, "priority": 2 }
      }
    }
  3. Add a wrapping preset:

    .specify/presets/a-wrap/preset.yml

    provides:
      templates:
        - type: template
          name: review-template
          file: wrapper.md
          strategy: wrap

    .specify/presets/a-wrap/wrapper.md

    BEFORE
    {CORE_TEMPLATE}
    AFTER
    
  4. Add a base preset whose content intentionally contains the literal token:

    .specify/presets/b-base/preset.yml

    provides:
      templates:
        - type: template
          name: review-template
          file: base.md
          strategy: replace

    .specify/presets/b-base/base.md

    BASE {CORE_TEMPLATE}
    
  5. Run:

    timeout 5 .specify/scripts/bash/resolve-template.sh review-template
    echo $?

Expected Behavior

The resolver replaces each {CORE_TEMPLATE} occurrence that existed in the original wrapper exactly once, preserves literal token text contained in the inserted base content, and terminates. For the example above, the output should contain:

BEFORE
BASE {CORE_TEMPLATE}
AFTER

Actual Behavior

The process consumes CPU until timeout terminates it with exit code 124. Each iteration replaces a token with base content that contains another token, so the loop condition remains true forever.

Specify CLI Version

1.0.3.dev0

AI Agent

Codex CLI

Operating System

Linux 7.2.0 x86_64

Python Version

Python 3.14.5 (Specify CLI runtime)

Error Logs

$ timeout 5 .specify/scripts/bash/resolve-template.sh review-template
$ echo $?
124

Additional Context

A single-pass replacement over an unmodified rest variable fixes the issue: consume tokens only from the original wrapper while appending content to a separate output buffer.

The issue was found while reviewing generated Spec Kit tooling in mforce/cluckwork#635. A verified local fix is in commit c70a5ad1.

I searched open and closed issues and PRs using {CORE_TEMPLATE}, wrap strategy, infinite loop, hang, and placeholder-reintroduction terms. Related issues #3991 and #4044 concern frontmatter corruption during wrap composition; they do not cover this non-terminating Bash replacement loop.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions