Skip to content

fix(bulk-archive): bulk archive nests a change inside an existing archive target instead of failing it #1827

Description

@choi138

fix(bulk-archive): bulk archive nests a change inside an existing archive target instead of failing it

Version: @fission-ai/openspec@1.13.0 (also present on main as of 2026-09-10)

Summary

The bulk-archive workflow tells the agent to mv a change into the archive without first checking whether the target directory already exists. The single-change archive workflow does check. When the target exists, POSIX mv moves changeRoot inside it and exits 0, so the agent records the change as archived successfully.

Where

skills/openspec-bulk-archive-change/SKILL.md, step 8c (L200-L206):

   c. **Perform the archive**:

      Target name: use the change name as-is when it already starts with a `YYYY-MM-DD-` prefix; otherwise prepend the current date as `YYYY-MM-DD-<name>` (same rule as `openspec archive`).

      ```bash
      mkdir -p "<planningHome.changesDir>/archive"
      mv "<changeRoot>" "<planningHome.changesDir>/archive/<target-name>"
      ```

There is no existence check between the target-name rule and the mv.

Why this looks unintended rather than by design

Three places in the same workflow already assume the check happens:

  1. Guardrails, L324 — "If archive target exists, fail that change but continue with others"
  2. Failure output template, L239 — lists some-change: Archive directory already exists as an expected outcome
  3. Partial-success output, L301 — same failure string

Step 8c never produces that outcome, so the guardrail and both output templates describe behavior the steps do not implement.

The single-change workflow does implement it. skills/openspec-archive-change/SKILL.md L138-L140:

   **Check if target already exists:**
   - If yes: Fail with error, suggest renaming existing archive or using different date
   - If no: Move `changeRoot` to the archive directory

Both workflows cite "same rule as openspec archive" for the target name, so the divergence in the following step reads as an omission in the bulk path.

Reproduction

  1. Archive change add-auth on 2026-09-10 → openspec/changes/archive/2026-09-10-add-auth/
  2. Create another change named add-auth, complete it the same day
  3. Run the bulk-archive workflow and select it

Expected: that change is reported Failed: Archive directory already exists, other changes in the batch continue.

Actual: mv succeeds with exit 0 and produces

openspec/changes/archive/2026-09-10-add-auth/2026-09-10-add-auth/

The nested change is reported as archived successfully. openspec list no longer shows it, and the archive entry for the first change now contains a second .openspec.yaml at a depth nothing scans.

Same result whenever the target name collides — most likely on a same-day retry after a partially failed batch, or on a change whose name already carries a YYYY-MM-DD- prefix.

Suggested fix

Add the existence check to step 8c, worded to match the single-change workflow and the outcome already declared in step 8d:

   c. **Perform the archive**:

      Target name: use the change name as-is when it already starts with a `YYYY-MM-DD-` prefix; otherwise prepend the current date as `YYYY-MM-DD-<name>` (same rule as `openspec archive`).

      **Check if target already exists:**
      - If yes: record this change as Failed with `Archive directory already exists`, leave `changeRoot` intact, and continue with the remaining changes
      - If no: move `changeRoot` to the archive directory

      ```bash
      mkdir -p "<planningHome.changesDir>/archive"
      mv "<changeRoot>" "<planningHome.changesDir>/archive/<target-name>"
      ```

Failing only that change and continuing matches guardrail L324 and keeps the batch semantics intact.

Following the layout of #1716, the same edit would apply to src/core/templates/workflows/bulk-archive-change.ts, with coverage in test/core/templates/.

Happy to open the PR if this is the direction you want.

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