Skip to content

fix(workflows): let approved specs merge on their own again - #11868

Merged
MarkusNeusinger merged 4 commits into
mainfrom
fix/spec-create-claude-bot-pr
Sep 27, 2026
Merged

MarkusNeusinger merged 4 commits into
mainfrom
fix/spec-create-claude-bot-pr

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

Summary

Since GitHub's 2026-06-11 change, workflow runs on a pull request that GITHUB_TOKEN opens or updates sit at action_required until a human approves them. spec-create.yml opened the spec PR with GITHUB_TOKEN, so the main ruleset's required checks (Run Linting, Run Tests, Run Frontend Tests, Changelog (fragment)) never ran. The merge job then failed with "the base branch policy prohibits the merge" until the owner approved the runs by hand (#11847).

This PR implements option B, the one the owner chose. Claude opens the spec PR as claude[bot], using the Claude GitHub App token that claude-code-action already mints for the Claude step. This is the pattern daily-regen's spec polish uses, for example #10454, whose required checks ran without an approval. The merge job keeps GITHUB_TOKEN and never bypasses the ruleset: it verifies the PR, fails closed, and then lets auto-merge wait for the checks.

Plan

Create job

  • Both Claude prompts (create and retry) gain a step 9 that opens the PR with the same title and body as before, and no labels. The step reuses a PR that is already open. The retry prompt also gets a note on resuming a partial attempt.
  • The workflow looks the PR up by head branch afterwards. It fails with an issue comment, including a recovery recipe, if the PR is missing or was not opened by app/claude.
  • The duplicate path now ends cleanly: the steps after validation are gated on created=true.

Merge job

  • Runs only when the repository owner adds approved (github.event.sender.login == github.repository_owner).
  • Verifies the PR fail-closed:
    • exactly one open PR, from specification/<id> into main, not cross-repo;
    • author app/claude;
    • the body references the approved issue;
    • the diff, read locally from the exact head SHA with git diff --raw -z --no-renames origin/main...<sha>, touches only plots/<id>/specification.md, specification.yaml, and optional (implementations|metadata)(/[a-z]+)?/.gitkeep, all as regular 100644 files. The allowlist lives in the new automation/scripts/spec_pr_guard.py and has unit tests.
    • If any check fails, the job posts a refusal comment on the issue that tells the owner not to approve runs for such a PR or merge it by hand.
  • Merges with gh pr merge --squash --delete-branch --auto --match-head-commit <sha> on GITHUB_TOKEN.
  • Waits up to 15 minutes for MERGED. On timeout or a moved head, it turns auto-merge off again and says so on the issue.
  • Once MERGED is confirmed, it dispatches sync-postgres.yml (a GITHUB_TOKEN merge triggers no push workflows), then applies spec-ready, then comments. The comment no longer claims the spec is "synced to PostgreSQL"; it says the sync was dispatched.
  • Re-runs after the merge has landed skip straight to those post-merge steps.

Concurrency

  • The job-level spec-merge-main group is gone. GitHub keeps one pending run per group, and that shared group cancelled 7 of 10 batch approvals on 2026-06-10.
  • spec-request and approved share one lane per issue. Any other label gets its own lane, so it cannot cancel a queued approval.

Other changes

  • auto-update-pr-branches.yml skips specification/ PRs, as it already skips Dependabot. Its GITHUB_TOKEN update-branch commit would hold the spec PR's CI and move the verified head.
  • Docs (docs/workflows/overview.md, agentic/docs/project-guide.md, automation/scripts/README.md), the ci-changelog.yml comment, and a changelog fragment.
  • Author exemptions in ci-changelog.yml are not widened. The claude[bot] spec PR runs the gate and passes on the plots/ path exemption.

Test plan

  • actionlint 1.7.12 with shellcheck on the three changed workflows: clean.
  • uv run pytest tests/unit -q: 2010 passed, including 47 new spec_pr_guard tests. tests/unit/workflows: 324 passed.
  • ruff check, ruff format --check and mypy on the new script: clean.
  • uv run python -m tools.changelog check --base origin/main: passes.
  • Local simulation of the merge job's verify, merge and wait steps, extracted from the YAML, against a fake gh and a throwaway git origin: 19 of 19 scenarios pass. They cover a clean PR, a wrong author, an extra workflow file, a wrong issue reference, a fork or wrong base, no PR, two PRs, an already-merged PR, an unfetchable SHA, the pinned merge call, OPEN to MERGED, a moved head, a timeout, auto-merge turned off, a merge at an unverified head, and a closed PR.
  • Workflow changes have no local verification loop. The next real spec request is the live test. Watch it through: PR opened by claude[bot], CI runs without an approval, approved added, auto-merge lands, sync-postgres dispatched, spec-ready applied.
  • If that first claude[bot] spec PR shows "review required", check the ruleset parameter require_extra_approval_for_unattributed_changes (currently true on ruleset 10578859).

MarkusNeusinger and others added 2 commits September 27, 2026 00:48
Since GitHub's 2026-06-11 change, pull_request runs on a PR that
GITHUB_TOKEN opens or updates are held at action_required until a human
approves them. spec-create opened the spec PR with GITHUB_TOKEN, so the
ruleset's required checks never ran and the merge job failed with "the
base branch policy prohibits the merge" (#11847).

- Claude now opens the spec PR as claude[bot] with the Claude App token
  claude-code-action mints for the Claude step (the spec-polish pattern),
  in both the create and the retry prompt; title, body and (no) labels
  are unchanged. The workflow looks the PR up by head branch afterwards
  and fails loudly, with an issue comment, when it is missing or was not
  opened by the Claude app.
- The merge job runs only on the owner's `approved` label and verifies
  the PR fail-closed: open, specification/<id> into main, same repo,
  authored by app/claude, body references the issue, and a diff (read
  locally from the exact head SHA) limited to the spec files via the new
  automation/scripts/spec_pr_guard.py. It refuses with an issue comment
  otherwise, then runs `gh pr merge --squash --auto --match-head-commit`
  on GITHUB_TOKEN (no --admin), waits up to 15 minutes for MERGED
  (turning auto-merge off again on timeout or a moved head), dispatches
  sync-postgres.yml, and only then labels the issue spec-ready. Re-runs
  after a landed merge continue with the post-merge steps.
- The job-level `spec-merge-main` concurrency group, which cancelled
  queued batch approvals, is gone; the workflow-level group keeps one
  lane per issue for spec-request/approved and gives any other label its
  own lane.
- auto-update-pr-branches.yml skips specification/ PRs: its GITHUB_TOKEN
  merge commit would hold their CI and move the verified head.
- Docs (workflows overview, project guide, scripts README), the
  ci-changelog comment, and a changelog fragment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjHc4vXdEfWzM8G3FoYtGv
…he failure prefix

- The merge wait reads reviewDecision: if the main ruleset's extra
  approval for unattributed changes holds the Claude app's PR, the
  timeout says to approve the PR instead of advising a re-run that
  would only time out again.
- The merge job also requires triggering_actor == owner, so a re-run by
  someone else cannot act on the owner's earlier label.
- The failure comment no longer claims the spec was not merged when the
  wait step found it merged at an unexpected head.
- docs/contributing.md: only the repository owner's label counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjHc4vXdEfWzM8G3FoYtGv
Copilot AI balanced review requested due to automatic review settings September 26, 2026 23:06
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjHc4vXdEfWzM8G3FoYtGv

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

The duplicate flow still fails, the guard permits incomplete specifications, and suppressed IndexNow dispatches are not restored.

Review effort: Balanced
Findings: 3 Medium severity · 2 Low severity

Open (5)
What changed in this PR

This PR restores autonomous specification merging by having the Claude GitHub App create spec PRs and adding fail-closed merge validation.

Changes:

  • Moves spec PR creation to claude[bot].
  • Adds PR validation, guarded auto-merge, and explicit database synchronization.
  • Updates concurrency behavior, tests, documentation, and changelog guidance.
File Description
.github/​workflows/​spec-create.yml Reworks spec creation and merging.
.github/​workflows/​auto-update-pr-branches.yml Excludes specification branches from automatic updates.
.github/​workflows/​ci-changelog.yml Documents Claude-authored spec PR handling.
automation/​scripts/​spec_pr_guard.py Adds the merge file allowlist.
automation/​scripts/​README.md Documents the guard CLI.
tests/​unit/​automation/​scripts/​test_spec_pr_guard.py Tests guard paths, modes, and failures.
docs/​workflows/​overview.md Documents the revised specification pipeline.
docs/​contributing.md Clarifies owner-only approval.
agentic/​docs/​project-guide.md Updates internal workflow guidance.
changelog.d/​spec-create-claude-bot-pr.md Records the workflow fix.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/spec-create.yml
Comment thread .github/workflows/spec-create.yml
Comment thread automation/scripts/spec_pr_guard.py
Comment thread .github/workflows/ci-changelog.yml
Comment thread .github/workflows/spec-create.yml Outdated
@codecov

codecov Bot commented Sep 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot review on #11868:
- A duplicate stops Claude before it renames the issue, so 'Extract
  outputs' found no id and failed before the duplicate check could close
  the issue (pre-existing on main). It now hands an empty id to the
  validate step, which treats it like a missing spec directory.
- The merge guard now requires both specification.md and
  specification.yaml; a PR with only one allowed file no longer passes.
- The changelog-exemption wording in CLAUDE.md, copilot-instructions,
  pull_request.md and changelog.d/README.md says spec-create and spec
  auto-polish open their PRs as claude[bot] and pass as plots/-only.
- American spelling in the concurrency comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjHc4vXdEfWzM8G3FoYtGv
@MarkusNeusinger
MarkusNeusinger merged commit 0d618d5 into main Sep 27, 2026
22 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the fix/spec-create-claude-bot-pr branch September 27, 2026 09:28
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.

2 participants