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 exists as 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 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
- Archive change
add-auth on 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.
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 onmainas of 2026-09-10)Summary
The bulk-archive workflow tells the agent to
mva change into the archive without first checking whether the target directory already exists. The single-change archive workflow does check. When the target exists, POSIXmvmoveschangeRootinside it and exits0, so the agent records the change as archived successfully.Where
skills/openspec-bulk-archive-change/SKILL.md, step 8c (L200-L206):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:
some-change: Archive directory already existsas an expected outcomeStep 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.mdL138-L140: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
add-authon 2026-09-10 →openspec/changes/archive/2026-09-10-add-auth/add-auth, complete it the same dayExpected: that change is reported
Failed: Archive directory already exists, other changes in the batch continue.Actual:
mvsucceeds with exit 0 and producesThe nested change is reported as archived successfully.
openspec listno longer shows it, and the archive entry for the first change now contains a second.openspec.yamlat 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:
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 intest/core/templates/.Happy to open the PR if this is the direction you want.