Skip to content

[Bug]: Plan setup and prerequisite checks accept a whitespace-only plan template or plan.md #5035

Open

Description

@codegeek

Bug Description

1.2.2 added checks so that a missing or empty plan template no longer produces an empty plan.md, and so that check-prerequisites rejects an empty plan.md. Both checks test byte length, so a template or plan.md that contains only whitespace passes. setup-plan copies a whitespace-only template and exits 0, and check-prerequisites accepts the result. The same happens in the bash and Python scripts.

Steps to Reproduce

In a project on 1.2.2 with a feature directory containing spec.md (SPECIFY_FEATURE_DIRECTORY set to it):

Case 1, whitespace-only template:

printf '\n   \n\t\n' > .specify/templates/plan-template.md
python3 .specify/scripts/python/setup_plan.py --json
python3 .specify/scripts/python/check_prerequisites.py --json

Case 2, existing whitespace-only plan.md with a valid template:

printf '  \n\n' > specs/099-repro-plan/plan.md
python3 .specify/scripts/python/setup_plan.py --json
python3 .specify/scripts/python/check_prerequisites.py --json

Control, existing zero-byte plan.md:

: > specs/099-repro-plan/plan.md
python3 .specify/scripts/python/check_prerequisites.py --json

The bash scripts (setup-plan.sh --json, check-prerequisites.sh --json) behave the same.

Expected Behavior

A template or plan.md with no non-whitespace content is treated like an empty one: setup-plan refuses to create the plan from a whitespace-only template, and check-prerequisites fails with "plan.md is empty".

Actual Behavior

Case 1: Copied plan template to .../plan.md, exit 0; plan.md is 7 bytes of whitespace (\n \n\t\n); check_prerequisites.py --json prints {"FEATURE_DIR":"...","AVAILABLE_DOCS":[]}, exit 0.
Case 2: Plan already exists at .../plan.md, skipping template copy, exit 0; check_prerequisites.py --json exit 0.
Control: ERROR: plan.md is empty in ..., exit 1.

Specify CLI Version

1.2.2

AI Agent

Claude Code (also reproduced with the scripts run directly; agent-independent)

Operating System

macOS 26 (Darwin 25.6.0), arm64

Python Version

Python 3.15.0

Error Logs

(none; the scripts succeed when they should fail)

Additional Context

Code: setup_plan.py checks template_content == "" (~line 73); check_prerequisites.py checks paths.impl_plan.stat().st_size == 0 (~line 239); the bash equivalents are setup-plan.sh ~49-53 and check-prerequisites.sh ~151. A .strip() / non-whitespace test would cover both cases.

Related, not reproduced: setup-plan writes the template directly to plan.md, so an interrupted write could leave a partial non-empty file that the check then accepts. Writing to a temporary file and renaming it would avoid that.

AI Disclosure

The issue was first flagged by Sourcery's automated PR review on a downstream project. Claude Code (Claude Opus 5.5, human-supervised) reproduced it with the commands above and drafted this issue; I reviewed it before filing.

Activity

  1. github-actions commented on Oct 20, 2026

    @github-actions
    Contributor

    Bug assessment — whitespace-plan-template: Valid · severity medium


    Bug Assessment: Plan setup and prerequisite checks accept a whitespace-only plan template or plan.md

    Report (summarized)

    The reporter states that Specify CLI 1.2.2 added empty-template and empty-plan checks, but those checks use byte length. A plan template or existing plan.md containing only whitespace therefore passes. The report provides Python and Bash reproduction commands, expected failures, observed successful exits, and notes the same behavior in the Bash and Python scripts. No comments added further reproduction context.

    Environment reported: macOS 26, arm64, Python 3.15.0, Claude Code; the behavior is described as agent-independent.

    Symptom

    setup-plan copies a whitespace-only plan-template.md into plan.md, and check-prerequisites accepts a whitespace-only existing plan.md, both with success status. Expected behavior is to treat files with no non-whitespace content as empty and fail with the existing empty-plan/template diagnostics.

    Reproduction

    1. In a Spec Kit project with a feature directory selected through SPECIFY_FEATURE_DIRECTORY, write a template containing only whitespace, such as newlines, spaces, and tabs.
    2. Run python3 .specify/scripts/python/setup_plan.py --json.
    3. Run python3 .specify/scripts/python/check_prerequisites.py --json.
    4. Alternatively, create a whitespace-only specs/.../plan.md while a valid template exists, then run the same two commands.
    5. Run the Bash twins (setup-plan.sh --json and check-prerequisites.sh --json) to observe the same byte-length behavior.

    The report's zero-byte control case is rejected as expected. [NEEDS CLARIFICATION: whether the same reproduction has been run through the installed PowerShell command on Windows; the current PowerShell source shows the same length-only checks.]

    Suspected Code Paths

    • scripts/python/setup_plan.py:73 — rejects only when template_content == ""; whitespace remains a non-empty string and is written to plan.md.
    • scripts/python/check_prerequisites.py:239 — rejects only when paths.impl_plan.stat().st_size == 0; whitespace bytes make the file pass.
    • scripts/bash/setup-plan.sh:49 — [[ ! -s "$template_file" ]] checks only whether the resolved template has zero bytes.
    • scripts/bash/check-prerequisites.sh:151 — [[ ! -s "$IMPL_PLAN" ]] likewise checks only file size.
    • scripts/powershell/setup-plan.ps1:51 — $content.Length -eq 0 does not classify whitespace-only content as empty.
    • scripts/powershell/check-prerequisites.ps1:111 — file .Length -eq 0 does not classify whitespace-only plan.md as empty.
    • tests/test_setup_plan_python_parity.py:136 — the existing parity test explicitly expects a whitespace-only template to succeed, documenting the current behavior rather than the intended behavior.

    Root Cause Hypothesis

    The empty-content validation was implemented as a byte/character count check rather than a semantic content check. Because whitespace characters contribute to both string length and file size, all three script implementations accept files that contain no usable plan text. Confidence: high; the relevant checks directly support the reported outcomes.

    Proposed Remediation

    Preferred: Change the empty-content predicate in all three script variants to require at least one non-whitespace character. In Python, use a stripped-content check for the resolved template and read/check the plan content (or an equivalent whitespace-aware file check) instead of st_size. In Bash, replace -s with a whitespace-aware predicate over the template and plan files. In PowerShell, use .Trim().Length -eq 0 for the template and plan content. Preserve the existing error messages, exit codes, JSON behavior, and no-overwrite behavior. Keep the implementations behaviorally aligned across languages.

    Update parity and direct tests to cover whitespace-only templates and existing plans in JSON and text modes, asserting failure, empty stdout in JSON mode where applicable, no plan creation for an invalid template, and preservation of existing files. Retain zero-byte and valid-content coverage.

    Alternatives (optional):

    • Add a shared cross-language helper or generated validation contract to reduce future parity drift; this improves consistency but adds maintenance or generation complexity.
    • Normalize/remove whitespace before writing; not preferred because it silently changes authored template/plan content rather than rejecting unusable input.

    Files likely to change:

    • scripts/python/setup_plan.py
    • scripts/python/check_prerequisites.py
    • scripts/bash/setup-plan.sh
    • scripts/bash/check-prerequisites.sh
    • scripts/powershell/setup-plan.ps1
    • scripts/powershell/check-prerequisites.ps1
    • tests/test_setup_plan_python_parity.py
    • Tests covering check-prerequisites parity and empty-plan validation, likely tests/test_check_prerequisites_python_parity.py or related setup-task tests.

    Tests to add or update:

    • For each available script variant, a whitespace-only template must cause setup-plan to exit 1 without creating plan.md.
    • For each available script variant, a whitespace-only existing plan.md must cause check-prerequisites to exit 1 with the empty-plan diagnostic.
    • Cover spaces, tabs, and line endings in both JSON and text modes, while confirming valid non-whitespace plans continue to pass.
    • Replace the current parity expectation at tests/test_setup_plan_python_parity.py:136 that treats a whitespace-only template as valid.

    Risks & Considerations

    • Cross-language whitespace definitions must remain aligned, especially for tabs, CRLF line endings, and Unicode whitespace.
    • Reading files to validate content has negligible expected cost for small Markdown plans, but error handling for unreadable files should preserve repository-standard failures rather than silently treating them as empty.
    • Rejecting previously accepted whitespace-only files is an intentional behavior correction; users with such files will need to populate or remove them.
    • The related interrupted-write concern is separate from this bug and should not be conflated with the whitespace validation fix; atomic replacement could be considered independently.

    Open Questions

    • [NEEDS CLARIFICATION: Has the PowerShell implementation been reproduced on Windows, or should it be covered only by parity tests?]
    • [NEEDS CLARIFICATION: Should the fix define non-whitespace using the host language's standard whitespace set, or specifically ASCII Markdown whitespace?]

    Generated by 🐛 Assess Bug from Labeled Issue for #5035 · copilot · gpt52codex · 1.9 AIC · ⌖ 0.603 AIC · ⊞ 22.3K · ◷

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions