Repository navigation
fix(bulk-archive): bulk archive nests a change inside an existing archive target instead of failing it #1827
Description
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:
- Guardrails, L324 — "If archive target exists, fail that change but continue with others"
- Failure output template, L239 — lists
some-change: Archive directory already existsas an expected outcome - 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 directoryBoth 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
- Archive change
add-authon 2026-09-10 →openspec/changes/archive/2026-09-10-add-auth/ - Create another change named
add-auth, complete it the same day - 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
fix(bulk-archive): check the archive target before moving changeRoot (F…