Skip to content

fix(templates): don't archive a change before its spec sync finishes#1394

Merged
clay-good merged 6 commits into
mainfrom
fix/archive-wait-for-spec-sync
Jul 20, 2026
Merged

fix(templates): don't archive a change before its spec sync finishes#1394
clay-good merged 6 commits into
mainfrom
fix/archive-wait-for-spec-sync

Conversation

@clay-good

@clay-good clay-good commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Status

LGTM — ready to review. Fixes #1393. Agent-facing template prose plus the repo's own spec; no CLI or parser code is touched.

What was wrong

The generated openspec-archive-change skill told the agent to hand the spec sync to a background task, then archive:

If user chooses sync, use Task tool (subagent_type: "general-purpose", …). … For any other choice, proceed to archive.

Step 5 then runs mv "<changeRoot>" ".../archive/YYYY-MM-DD-<name>". Nothing in between required the sync to have finished.

Background tasks don't block, so the archive moves the delta specs out from under a sync that is still reading them. The change lands in archive/, openspec/specs/ never receives the requirements, and the summary still prints Specs: ✓ Synced — the user is told the sync happened when it did not. Where the delegation happens to block, it works, so correctness depended on scheduling rather than on the instructions.

How it was fixed

The sync runs inline. Step 4 now runs the openspec-sync-specs workflow inline and waits, instead of delegating it. Removing the background hop removes the race at its source rather than asking prose to win a scheduling argument.

Verification follows delta semantics. Before step 5, the agent re-checks every capability that has a delta spec — not just the ones the sync claims it touched, so a silently skipped capability can't escape:

Delta Must be true after sync
ADDED requirement present
MODIFIED the scenario/description changes named in the delta are present, other scenarios intact
REMOVED requirement gone
RENAMED present under the new name, absent under the old

This is the same comparison step 4 already runs before prompting, so the check can't drift from what openspec-sync-specs actually does: a successful sync leaves nothing left to apply.

If the sync failed or a capability doesn't match, the agent reports what differs and stops. Nothing has moved at that point, so the user can fix it and retry.

Getting this predicate right took two passes, and both mistakes are now covered by tests:

  • Checking only that requirements were present would read a correct REMOVED-only sync as a failure and block a valid archive.
  • Checking MODIFIED for mere presence is vacuous — a MODIFIED requirement already exists before the sync — so a no-op sync would have passed the gate for the most common delta shape.

Tool-neutral wording. This template is also the slash-command source for ~28 non-Claude tools, and skills are removed entirely under commands-only delivery. An earlier revision named the Claude Code "Skill tool" and run_in_background; both variants now use the runtime-neutral phrasing bulk-archive-change already uses.

Two consistency fixes fall out of the same area: the prompt options are now routed explicitly (an unrecognized answer re-asks instead of defaulting to archive), and the summary's ✓ Synced line is marked conditional on the step 4 verification.

Scope

File Change
src/core/templates/workflows/archive-change.ts Inline sync + verification gate, both variants
openspec/specs/opsx-archive-skill/spec.md Was still specifying "proceed with archive regardless of sync choice"
skills/openspec-archive-change/SKILL.md Regenerated (pnpm build && pnpm generate:skills)
test/core/templates/skill-templates-parity.test.ts 3 re-pinned golden hashes + a new assertion
.changeset/archive-waits-for-spec-sync.md Patch changeset

bulk-archive-change never delegates its sync, so it doesn't have this race and is left alone.

Proof it works

openspec validate --specs --strict → 36 passed, 0 failed.

The parity test gates the archive on a completed spec sync (#1393) pins the invariants in both the generated skill and the command template: the sync runs inline, delegation to a background task is prohibited, verification is bound to the delta specs on disk, and the MODIFIED/REMOVED/RENAMED predicates hold. All seven asserted strings are absent from main, so it fails without this change — verified, not assumed.

Test suite: 2028 tests. Local failures are the 17 known environment-only zsh-installer cases (oh-my-zsh present, per CLAUDE.md); under heavy parallel load a few CLI-spawn tests can also time out. CI is green on Linux, macOS and Windows.

Getting the fix

These are generated assets, so existing projects pick this up by upgrading and re-running openspec update, which regenerates the skills and commands from these templates. New projects get it at openspec init.

Notes / nits

  • Other open PRs touch this template and re-pin the same hash constants. None edits these lines, so there's no semantic conflict — but whichever merges second will hit a git conflict in skill-templates-parity.test.ts and needs pnpm build && pnpm generate:skills plus fresh hashes.
  • Known interaction, not introduced here: a MODIFIED delta against a capability with no main spec can't satisfy the gate, because sync-specs step 4d creates a new main spec from ADDED requirements only. Today that surfaces as a blocked archive with a report naming the mismatch, instead of a silently incomplete spec. "Archive without syncing" remains available as the escape hatch.
  • Worth a follow-up: bulk-archive-change reports "N delta specs synced" with no verification, so the silent-false-success half of Generated archive skill can race the spec sync: archive proceeds without waiting for the dispatched sync subagent #1393 survives there.

🤖 Generated with Claude Code using Claude Opus 4.8

Summary by CodeRabbit

  • Bug Fixes
    • Updated the openspec-archive-change and opsx:archive workflows to run spec synchronization inline and wait for it to complete before archiving.
    • After syncing, the workflows re-check all delta capabilities (added/modified/removed/renamed). If there’s a mismatch, sync fails, or the user cancels, archiving is blocked and success is not reported.
  • Tests
    • Updated template hash expectations and added coverage for the “gate archive on completed sync verification” behavior.
  • Documentation
    • Clarified messaging and guardrails around “synced,” “sync skipped,” and “sync failed.”

The generated openspec-archive-change skill dispatched the spec sync to a
subagent via the Task tool and then moved changeRoot in the very next step,
with nothing requiring it to wait. Where subagents run asynchronously, the
archive relocates the delta specs out from under the running sync, so the
change is archived while openspec/specs/ is never updated — and the success
summary still reports "Specs: ✓ Synced".

Step 4 now requires waiting for the dispatched sync to return, verifying the
synced requirements are present in the main spec, and stopping without
archiving if either check fails. Adds a matching guardrail bullet and a
parity assertion so the gate cannot silently disappear again.

Fixes #1393

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@clay-good
clay-good requested a review from TabishB as a code owner July 20, 2026 17:24
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Archive workflows now run spec synchronization inline, verify synced requirements in main specs, and stop before archiving on cancellation, sync failure, or mismatches. Templates, parity tests, the archive specification, and patch release metadata were updated.

Changes

Archive sync gating

Layer / File(s) Summary
Define the archive synchronization gate
openspec/specs/opsx-archive-skill/spec.md, skills/openspec-archive-change/SKILL.md
The archive flow waits for inline synchronization, verifies ADDED/MODIFIED/REMOVED/RENAMED outcomes, and stops on cancellation, failure, or mismatches.
Apply gating to archive templates
src/core/templates/workflows/archive-change.ts
Both generated archive workflows prohibit archiving during an in-flight sync and require successful main-spec verification before moving the change.
Validate generated workflows and release metadata
test/core/templates/skill-templates-parity.test.ts, .changeset/archive-waits-for-spec-sync.md
Parity hashes and assertions cover the updated templates, and a patch Changeset documents the fix.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ArchiveWorkflow
  participant openspec-sync-specs
  participant MainSpec
  ArchiveWorkflow->>openspec-sync-specs: Run delta spec synchronization inline
  openspec-sync-specs-->>ArchiveWorkflow: Return completion status
  ArchiveWorkflow->>MainSpec: Verify capability requirements
  MainSpec-->>ArchiveWorkflow: Return match status
  ArchiveWorkflow->>ArchiveWorkflow: Archive only after successful verification
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: tabishb

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes make archive wait for inline spec sync and verify delta semantics before moving the change, matching #1393.
Out of Scope Changes check ✅ Passed All edits support the archive-sync fix and related tests/specs; no unrelated changes stand out.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: preventing archive until spec sync completes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/archive-wait-for-spec-sync

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Addresses review on #1394. The first pass asked the agent to "wait" for a
dispatched subagent, but subagents run in the background by default and the
wait is not reliably expressible in prose — the race survived. It also gated
the archive on the synced requirements being *present*, which a correct
REMOVED-only or RENAMED-only sync does not satisfy, turning a successful sync
into a hard block.

The sync now runs inline via the Skill tool, with a synchronous-subagent
fallback for harnesses that need one. Verification follows delta semantics:
ADDED/MODIFIED present, REMOVED gone, RENAMED under the new name, checked
across every capability the sync touched.

Also resolves the opsx command variant's contradiction with its own guardrail,
stops the summary reporting a checkmark that step 4 never verified, and updates
openspec/specs/opsx-archive-skill/spec.md, which still said the skill proceeds
with the archive regardless of the sync choice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openspec/specs/opsx-archive-skill/spec.md`:
- Around line 77-80: Clarify the archive workflow requirements around inline
/opsx:sync: verify ADDED and MODIFIED requirements are present, REMOVED
requirements are absent, and RENAMED requirements exist under the new name.
Distinguish an explicit “Archive without syncing” choice from a requested sync
that fails or remains unverified; only the former may proceed without
verification, while the latter must stop archiving. Update the conditions around
the archive decision to encode this contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0e719f9c-9456-4b5b-8aa1-25609687cfc1

📥 Commits

Reviewing files that changed from the base of the PR and between eaa14e0 and 2095c16.

📒 Files selected for processing (5)
  • .changeset/archive-waits-for-spec-sync.md
  • openspec/specs/opsx-archive-skill/spec.md
  • skills/openspec-archive-change/SKILL.md
  • src/core/templates/workflows/archive-change.ts
  • test/core/templates/skill-templates-parity.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • .changeset/archive-waits-for-spec-sync.md
  • skills/openspec-archive-change/SKILL.md
  • src/core/templates/workflows/archive-change.ts
  • test/core/templates/skill-templates-parity.test.ts

Comment thread openspec/specs/opsx-archive-skill/spec.md Outdated
… spec

The scenario said the agent verifies each capability "matches its delta",
which is ambiguous about what a match means — and a REMOVED-only sync
correctly leaves requirements absent. Spell out the predicate the template
implements, and separate an explicit "Archive without syncing" choice from a
requested sync that failed or could not be verified: only the former may skip
verification, the latter must stop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@clay-good clay-good changed the title fix(templates): wait for the spec sync before archiving a change fix(templates): don't archive a change before its spec sync finishes Jul 20, 2026
clay-good and others added 3 commits July 20, 2026 12:50
Second review pass on #1394.

The gate was weaker than it looked. "MODIFIED requirements present" is
vacuous — a MODIFIED requirement exists in the main spec before the sync runs,
so a no-op sync passed the check for the most common delta shape, which is the
exact symptom #1393 reports. "RENAMED under their new name" passed a sync that
copied rather than renamed, leaving both names behind. And scoping the re-check
to "every capability it touched" derived the verification set from the artifact
being verified, so a silently skipped capability escaped it.

Verification is now bound to the delta specs in artifactPaths.specs, covers the
changes each MODIFIED delta names, and requires RENAMED requirements to be gone
from the old name.

Separately, the previous pass named the Claude Code "Skill tool" and
run_in_background in a template that is also the slash-command source for ~28
other tools, where skills are removed entirely for commands-only delivery. Both
variants now use the runtime-neutral phrasing bulk-archive-change already uses.

Also: route the prompt options explicitly instead of defaulting unknown answers
to archive, tell the user a stopped archive is recoverable, and mark the summary
line as a conditional rather than literal text to copy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The verification predicate restated delta semantics in its own words, which
could drift from what openspec-sync-specs actually does. Anchor it instead to
the comparison step 4 already performs before prompting: a successful sync
leaves nothing to apply, so every capability must read as already synced. The
explicit ADDED/MODIFIED/REMOVED/RENAMED bullets stay as the definition of what
"nothing left to apply" means.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@alfred-openspec alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest commits close the prior verification holes: MODIFIED content is checked, RENAMED requires the old name to disappear, and the verification set comes from the delta inputs rather than the sync report. The focused seven-test parity suite passes locally at 035039f and the full CI/CodeQL matrix is green, so this is approved.

@clay-good
clay-good added this pull request to the merge queue Jul 20, 2026
Merged via the queue into main with commit b474f81 Jul 20, 2026
14 checks passed
@clay-good
clay-good deleted the fix/archive-wait-for-spec-sync branch July 20, 2026 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generated archive skill can race the spec sync: archive proceeds without waiting for the dispatched sync subagent

2 participants