Skip to content

A delta at specs/<capability>.md is green-lit by status/apply, rejected by validate, then archived without being merged #1869

Description

@dwin-gharibi

Description

validate and archive read a change's deltas only from
specs/<capability-path>/spec.md (discoverSpecFiles). The spec-driven schema
declares the specs artifact as generates: "specs/**/*.md", so status and
instructions apply count any markdown file under specs/ as the specs being
written.

A delta written as specs/user-auth.md, one level too shallow, therefore:

  1. is reported done by openspec status,
  2. gets all_done from openspec instructions apply with no warning (the
    fix(apply): warn when a change is ready to implement with no specs #1783 no-specs warning uses the same glob, so it does not fire),
  3. is rejected by openspec validate only as "Change must have at least one
    delta",
  4. and is archived by openspec archive with exit 0. Nothing is merged into
    openspec/specs/, and the output does not mention specs at all.

The authored requirement never reaches the main spec, and the change is filed
as done. The same happens to delta sections in a second file beside a
capability's spec.md (for example specs/user-auth/more.md).

Reproduction

In a project created by openspec init --tools claude:

openspec new change add-login
cd openspec/changes/add-login
# proposal.md, design.md and tasks.md written, every task checked
mkdir -p specs
cat > specs/user-auth.md <<'MD'
## ADDED Requirements
### Requirement: Password Login
The system SHALL let a user sign in with a password.

#### Scenario: Valid password
- **WHEN** a user submits a valid password
- **THEN** a session is created
MD
cd ../../..

openspec status --change add-login --json          # specs: done, isPlanningComplete: true
openspec instructions apply --change add-login --json   # state: all_done, no warnings
openspec validate add-login                         # exit 1: "No deltas found"
openspec archive add-login --yes                    # exit 0

Expected

No surface green-lights a change whose delta validate and archive cannot
read. At minimum, archive must not archive it as complete. docs/cli.md
says archive "Validates the change (unless --no-validate)", and here
validation fails while archive succeeds.

Actual

status: specs=done isPlanningComplete=True
apply: state=all_done warnings=None
validate rc=1 :: ✗ [ERROR] file: Change must have at least one delta. No deltas found. ...
archive (full output):
  | Task status: ✓ Complete
  | Change 'add-login' archived as '2026-09-11-add-login'.
exit=0   main specs after: (none)   archived delta still on disk: specs/user-auth.md

With the delta at specs/user-auth/spec.md instead, validate passes and
archive merges it (Totals: + 1).

Root cause

  • schemas/spec-driven/schema.yaml:51 declares the specs artifact as
    generates: "specs/**/*.md"; artifact completion and the apply warning in
    src/commands/workflow/instructions.ts (collectApplyWarnings) resolve that
    glob.
  • src/utils/spec-discovery.ts (discoverSpecFiles) reads only spec.md
    inside a capability folder. validate and archive use it.
  • src/core/archive.ts runs delta validation only when it finds delta content
    it can read, or a specs/spec.md at the root (validate accepts a delta spec.md directly under specs/ that archive silently drops #1385). Otherwise it takes the
    zero-delta path its own comment acknowledges: "An UNMARKED zero-delta change
    still archives with only non-blocking proposal warnings".

Impact

Delta specs are what OpenSpec asks an AI agent to write, and writing one file
per capability as specs/<capability>.md is a natural slip. Every surface an
agent checks says the specs are done, and archive then files the change with
its requirements silently discarded.

Environment

  • OpenSpec 1.13.0 (reproduced on the published npm package and on a build of
    main @ 9d4e597)
  • Node v22, Linux
  • Reproduced in a project created entirely by openspec init +
    openspec new change

Related: #1385 (a spec.md at the specs/ root, fixed). This is the same drop
for a capability-named file, which that fix does not cover.

Activity

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

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