iterate-pr
Skill141 starsChanged 24 days agoMITView on GitHub
Iterate on a PR until CI passes. Use when you need to fix CI failures, address review feedback, or continuously push fixes until all checks are green. Automates the feedback-fix-push-wait cycle.
- Commits and pushes
10 other projects use a version of this, 7 of them edited.
Adopted by
Most recently taken up by
- xfstudio/skillsedited, first seen Oct 13, 2026
- layer5io/sistentedited, first seen Oct 9, 2026
- meshery/schemasedited, first seen Oct 9, 2026
- meshery/meshkitedited, first seen Oct 8, 2026
- salesforce/cloudsplainingtheir own version, first seen Oct 8, 2026
- meshery/mesheryedited, first seen Oct 8, 2026
Used with
Projects with it also use quota-axi, gh-axi and 3 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
layer5ioSkillquota-axi
Report local Claude, Codex, Cursor, GitHub Copilot, and Grok quota windows via the quota-axi CLI - remaining percentages, reset times, and provider status read from local auth sources, with no routing, recommendation, or provider mutation. Use before deciding whether it is safe to keep spending a provider's quota, when the user asks about usage, rate limits, or remaining quota, or when comparing local provider headroom.
Also in 4 projects that use this
layer5ioSkillgh-axi
Operate GitHub through the gh-axi CLI - issues, pull requests, workflow runs, workflows, releases, repositories, labels, Projects (v2), Actions secrets and variables, search, and raw API access. Use whenever a task touches GitHub: listing or filing issues, reviewing or merging PRs, checking CI runs, triggering workflows, cutting releases, managing Projects boards, or managing Actions secrets/variables.
Also in 4 projects that use this
kunchenguidSkillchrome-devtools-axi
Control a Chrome browser session through the chrome-devtools-axi CLI - navigate, snapshot, click, fill forms, run JavaScript, inspect console and network, take screenshots, audit performance. Use whenever a task needs a real browser: opening or testing a web page, clicking through a flow, extracting page content, or debugging a website.
Also in 4 projects that use this
kunchenguidSkilllavish
Turn complex or visual agent responses into rich, reviewable HTML artifacts (HTML files) the user can annotate and send feedback on, using the lavish-axi CLI. Use when about to give a plan, comparison, diagram, table, code diff, report, or anything easier to grasp visually than as prose.
Also in 4 projects that use this
MicrosoftSkillcut-release
Use this skill to cut an APM release from the current worktree: assess whether the cycle since the last tag warrants a patch or minor bump (semver discipline against the merged-since-last-tag diff), sanitize the [Unreleased] CHANGELOG block into a dated version block with one concise "so what" entry per merged PR (drop internal-only churn, consolidate duplicates), bump pyproject.toml + uv.lock, run the CI-mirror lint chain, and open the release PR. Activate on "ship a release", "cut v0.x", "release prep", "bump and PR", "open release PR", "what kind of release do we need", or any phrasing that ends in opening a release PR -- even when the user does not say "skill". Stops BEFORE tagging; tagging stays a human gate that triggers the release workflow. Refuses to bump to a major (>= 1.0.0) version without explicit operator confirmation.
Also in 4 projects that use this
Other versions10 projects use a version of this.
Word for word · 0
None found.
Edited · 7
- meshery/meshery★ 12k12% same
- meshery/meshkit★ 23312% same
- layer5io/sistent★ 14012% same
- meshery/schemas★ 8512% same
- xfstudio/skills★ 911% same
- ai4brands-design/claude-skills★ 211% same
- StillScripts/cursor-agents-manager★ 112% same
Wrote their own version · 3
- salesforce/cloudsplaining★ 2.3ksame name
- getsentry/skills★ 1.1ksame name
- kumaran-is/claude-code-onboarding★ 35same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository17 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .github/copilot-instructions.mdCopilot instructions
- code-simplifierSkill
- openspec-apply-changeSkill
- openspec-archive-changeSkill
- openspec-bulk-archive-changeSkill
- openspec-continue-changeSkill
- openspec-exploreSkill
- openspec-ff-changeSkill
- openspec-new-changeSkill
- openspec-onboardSkill
- openspec-proposeSkill
- openspec-sync-specsSkill
- openspec-verify-changeSkill
- tasklessSkill
- worktrees-pnpmSkill
- tasklessSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
26 rules. 1 is common, 16 are distinctive, and 9 are very specific to this project.
1
Common
Most of the 10 edited versions we read keep these.
16
Distinctive
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 21
Important: All scripts must be run from the repository root directory (where .git is located), not from the skill directory.
- Common: line 163
Stop if no PR exists for the current branch.
- Very specific: line 167
Run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.cjs to get categorized feedback already posted on the PR.
- Distinctive: line 179
Check for similar issues in nearby code or related files
- Distinctive: line 186
Never silently ignore review bot feedback — always verify the finding
- Distinctive: line 216
After processing a feedback item, acknowledge it on the PR so the trail shows what was addressed.
- Very specific: line 278
If the gh/GraphQL call fails, log and continue — do not block the workflow.
- Very specific: line 282
Run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.cjs to get structured failure data.
- Distinctive: line 284
Wait if pending: If review bot checks (sentry, warden, cursor, bugbot, seer, codeql) are still running, wait before proceeding—they post …
- Very specific: line 387
Read the log_snippet and trace backwards from the error to understand WHY it failed — not just what failed
- Distinctive: line 388
Read the relevant code and check for related issues (e.g., if a type error in one call site, check other call sites)
- Distinctive: line 392
Do NOT assume what failed based on check name alone—always read the logs.
- Distinctive: line 396
Before committing, verify your fixes locally:
- Distinctive: line 398
If you fixed a test failure: re-run that specific test locally
- Distinctive: line 399
If you fixed a lint/type error: re-run the linter or type checker on affected files
- Distinctive: line 402
If local verification fails, fix before proceeding — do not push known-broken code.
- Very specific: line 414
Run node ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.cjs to get current CI status
- Distinctive: line 415
If all checks passed → proceed to exit conditions
- Distinctive: line 416
If any checks failed (none pending) → return to step 5
- Very specific: line 417
If checks are still pending: a.
- Very specific: line 422
After all checks pass, do a final feedback check: sleep 10, then run node ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.cjs.
- Distinctive: line 426
If step 7 required code changes (from new feedback after CI passed), return to step 2 for a fresh cycle.
- Distinctive: line 439
If waiting produced code changes, return to step 2 for a fresh cycle.
- Distinctive: line 441
Success: All checks pass, post-CI feedback re-check is clean (no new unaddressed high/medium feedback including review bot findings, and no …
- Distinctive: line 445
Stop: No PR exists, branch needs rebase.
- Very specific: line 449
If scripts fail, use gh CLI directly:
“Common” is measured: kept by at least 6 in 10 of the 10 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
<!--
Forked from Sentry's iterate-pr skill (Apache-2.0):
https://github.com/getsentry/skills/tree/main/skills/iterate-pr
Substantially modified: stack-aware tooling, LOGAF feedback bucketing,
self-review handling, and pending-reviewer tracking.
-->Iterate on PR Until CI Passes
Continuously iterate on the current branch until all CI checks pass and review feedback is addressed.
Requires: GitHub CLI (gh) authenticated, and Node (already required to work in this repo).
The scripts are zero-dependency CommonJS — stdlib only, no package to install and no second toolchain. Each one exports its logic and is covered by a *.test.cjs suite beside it, which Validate runs with node --test. When you change a script, change its test.
Important: All scripts must be run from the repository root directory (where .git is located), not from the skill directory. Use the full path to the script via ${CLAUDE_SKILL_ROOT}.
Shell gotcha: the agent's Bash tool MAY run under a non-bash shell (e.g. zsh). Always check the current shell before running commands (echo "$0" / ps -p $$ -o comm=). It matters because zsh does NOT word-split unquoted variables the way bash does — for x in $list; do … iterates once over the whole string, not per token, which can silently send a loop down the wrong path. So any multi-step orchestration (cascade rebases, loops over branches/PRs, array iteration) MUST be wrapped in a bash <<'EOF' … EOF heredoc (or bash -c) so the semantics are guaranteed regardless of the login shell — never write it as an inline loop. For destructive git fan-out (rebase + force-push loops), add a guard that refuses to push if a rebase balloons — e.g. abort when git rev-list --count <parent>..HEAD exceeds the branch's own commit count, which catches a rebase that landed on the wrong parent before it reaches the remote.
The bundled scripts sidestep all of that, and you should follow their pattern. They spawn gh and git with an argv array and no shell, so nothing is word-split, glob-expanded, or re-read for metacharacters, and a branch name containing a space or a * is just a string. They also never pipe through sed, awk, cut, or xargs: a command runs, its output is parsed in Node, and the next command is invoked from the parsed values. When you need a step they do not cover, prefer that shape — run, parse in Node, run again — over a shell pipeline whose behaviour depends on which shell you got.
Bundled Scripts
scripts/shared.cjs holds what they have in common — running gh and git, and deriving stack lineage from the open PRs. It is a library, not a command.
scripts/fetch_pr_checks.cjs
Fetches CI check status and extracts failure snippets from logs.
node ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.cjs [--pr NUMBER]Returns JSON:
{
"pr": { "number": 123, "branch": "feat/foo" },
"summary": { "total": 5, "passed": 3, "failed": 2, "pending": 0 },
"checks": [
{ "name": "tests", "status": "fail", "log_snippet": "...", "run_id": 123 },
{ "name": "lint", "status": "pass" }
]
}scripts/fetch_pr_feedback.cjs
Fetches and categorizes PR review feedback using the LOGAF scale.
node ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.cjs [--pr NUMBER]Returns JSON with feedback categorized as:
high- Must address before merge (h:, blocker, changes requested)medium- Should address (m:, standard feedback)low- Optional (l:, nit, style, suggestion)bot- Informational automated comments (Codecov, Dependabot, etc.)resolved- Already resolved threads, and top-level comments carrying our own 🎉 acknowledgementreview_in_progress- A review bot's placeholder comment, posted the instant it was triggered and not yet edited to its finished form. Not feedback yet — see below.review_summary- A review's top-level narration, bucketed structurally rather than by content. Surfaced and counted (summary.review_summaries), never prompted on — see below.
Review bot feedback (from Sentry, Warden, Copilot, Cursor, Bugbot, CodeQL, etc.) appears in high/medium/low with review_bot: true — it is NOT placed in the bot bucket.
That includes Claude posting as github-actions, which happens when a workflow hands claude-code-action github_token. That login is shared with informational bots, so it is decided by content: inline review threads from it, and top-level comments carrying a claude-code-action signature (Claude finished @…, a placeholder, a [View job](…/actions/runs/N) link), are review feedback. Other github-actions comments stay in bot.
A review bot's comment existing is not a completion signal, and neither is its created_at or a green check run. The Claude review bot posts a comment the instant it is triggered and edits that same comment in place as it works — a run has been observed to report success while the body still read "Review in progress" with unchecked boxes. fetch_pr_feedback.cjs detects this by checking whether the body still opens with the in-progress marker (a review that happens to discuss this behaviour mid-body is not mistaken for an unfinished one) and reports the count as summary.review_in_progress instead of bucketing the placeholder as ordinary feedback. Before treating needs_attention: 0 as "reviewed, nothing found," check summary.review_in_progress — a nonzero count means the review hasn't finished, not that it found nothing. When one or more are present, action_required says so and outranks every other bucket, since a caller must not stop just because the buckets it can already see look clean.
Self-review feedback (from the PR author) appears in high/medium/low with self_review: true. Because you can't "Request changes" on your own PR, a self-review lands as COMMENTED review summaries and ordinary review threads rather than changes-requested items — these are surfaced (not dropped). A self-review thread (an inline comment) is bucketed by content like any other, defaulting to medium absent a h:/m:/l: prefix. A self-review summary is bucketed structurally, not by content (see below), landing in review_summary absent a marker. Treat self_review items the same as any other human feedback in step 3.
A review's top-level summary is bucketed structurally, not by its content. A summary narrates a review's findings, so it always contains finding vocabulary — often negated ("not a blocker", "no security issue found") — and no content-pattern list survives that. A CHANGES_REQUESTED summary from a reviewer who isn't the PR author is always high; an explicit h:/m:/l: marker still wins over everything; absent both, it goes to its own review_summary bucket, counted as summary.review_summaries.
That bucket is deliberately not low. low means "an optional suggestion, ask the user which to address", and step 3 presents low items as a numbered list for exactly that. A review that found nothing proposes no work, so filing it there trades a false high for a false prompt and asks someone to triage a summary with nothing in it. review_summary is surfaced and counted without being actionable, the same shape review_in_progress uses. Do not prompt on these, and do not treat one as a finding. The findings a review raises still arrive separately as inline review-thread comments, which are classified by content as usual.
Each feedback item may also include:
thread_id- GraphQL node ID for inline review comments (used for replies)pending_reviewers(insummary) - count of requested reviewers who have not submitted yet;pr.requested_reviewerslists them
scripts/resolve_pr_threads.cjs
Resolves PR review threads by their GraphQL node IDs — the bulk equivalent of the resolveReviewThread mutation below. Prefer it when closing out several threads at once.
node ${CLAUDE_SKILL_ROOT}/scripts/resolve_pr_threads.cjs THREAD_ID [THREAD_ID ...]Returns JSON:
{
"resolved": ["PRRT_abc123", "PRRT_def456"],
"failed": [],
"already_resolved": ["PRRT_ghi789"]
}scripts/stack_status.cjs
Reports the health of a PR stack — prefer this over hand-rolled git rev-list/merge-base shell loops (which are zsh-fragile). Lineage comes from the open GitHub PRs (each PR's head → base), the shared source of truth — no local config. For every branch with a parent it prints ahead/behind vs origin, own-commit count, and whether it is cleanly stacked or DIVERGED (parent tip is not an ancestor → needs a restack). Operates purely on refs, independent of the checked-out branch.
node ${CLAUDE_SKILL_ROOT}/scripts/stack_status.cjs [--root <branch>]scripts/propagate_stack.cjs
Cascade-rebases a branch's descendants onto their parents to carry a fix up the stack — a focused restack that (unlike a whole-stack sync) never rebases onto the latest main, so fix-propagation stays decoupled from main-reconciliation. Lineage comes from the open GitHub PRs (head → base). It is topological (parent before child) and guarded: a balloon guard resets-without-pushing if a rebase lands on the wrong parent, and it stops on the first conflict for manual reconcile. Always prefer this to an inline rebase loop.
It replays from where the child forked, not from a merge-base. This matters
whenever a parent was rewritten, which is the normal case here: you fixed review
feedback on the parent, and that is why you are propagating at all. A plain
git rebase <parent> picks its upstream by merge-base, which after a rewrite
falls back to an older common ancestor — so the parent's own superseded commits
get replayed on top of their replacements. It surfaces as a conflict in files
the child never touched, and the obvious resolution ("take mine") silently
discards the parent's newer work. The child's own tests still pass, because the
change that was lost belongs to the parent, and under rebase-and-merge that
stale content then reaches main and reverts a fix that already landed.
The upstream is resolved from three sources, most reliable first: the parent's
pre-rebase tip when this run rebased it, git merge-base --fork-point when the
rewrite happened elsewhere (a parent restacked by hand, or an earlier run), and
the parent itself when it was only appended to. The balloon guard counts from
that same upstream, so its expectation cannot be inflated by the parent's
superseded commits.
node ${CLAUDE_SKILL_ROOT}/scripts/propagate_stack.cjs --root <branch> [--dry-run] [--no-push] [--no-sign]Every replayed commit is GPG-signed. A rebase writes new commits, and git signs them only when told to, so a stack signed with commit -S comes back unsigned from a plain rebase. The script passes --gpg-sign, and a signing failure (usually a locked key) is reported as one, not as a conflict. --no-sign opts out for a repository that does not sign.
One failure path leaves the repo on another branch. Normal completion, a
rebase conflict, a balloon guard trip, and a push failure all checkout the
branch you started on before returning. The exception is when checking out a
child fails (exit 6) — most often because that branch is checked out in a
worktree — which returns immediately, leaving you on whichever branch it had
reached.
That matters because anything you do next inherits it as a base: branching "from
main" right after one of these silently creates a branch on top of the whole
stack, and the resulting PR carries every stack commit plus an OpenSpec change
directory it never touched. After a non-zero exit, run git checkout main (or
git checkout -B <new> origin/main), and check git log --oneline origin/main..HEAD
before opening the PR.
Workflow
1. Identify PR
gh pr view --json number,url,headRefNameStop if no PR exists for the current branch.
2. Gather Review Feedback
Run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.cjs to get categorized feedback already posted on the PR.
3. Handle Feedback by LOGAF Priority
Auto-fix (no prompt):
high- must address (blockers, security, changes requested)medium- should address (standard feedback)
When fixing feedback:
- Understand the root cause, not just the surface symptom
- Check for similar issues in nearby code or related files
- Fix all instances, not just the one mentioned
This includes review bot feedback (items with review_bot: true). Treat it the same as human feedback:
- Real issue found → fix it
- False positive → skip, but explain why in a brief comment
- Never silently ignore review bot feedback — always verify the finding
Prompt user for selection:
low- present numbered list and ask which to address:
Found 3 low-priority suggestions:
1. [l] "Consider renaming this variable" - @reviewer in api.py:42
2. [nit] "Could use a list comprehension" - @reviewer in utils.py:18
3. [style] "Add a docstring" - @reviewer in models.py:55
Which would you like to address? (e.g., "1,3" or "all" or "none")Surface, never a fix and never a prompt:
review_summary- a review's own narration, bucketed structurally rather than by content (see step 2's category list). It is not a finding to fix and not a suggestion to triage — reply to it per "Replying to Comments" below like any other surfaced item, but do not present it in the low-priority numbered list and do not count it towardneeds_attention.
Skip silently:
resolvedthreadsbotcomments (informational only — Codecov, Dependabot, etc.)
Neither address nor skip — wait:
review_in_progressitems. There is nothing to fix yet; the bot hasn't finished writing its findings. Do not treat these asbotnoise or as a clean review. See the "Wait if pending" note in step 4.
Replying to Comments
After processing a feedback item, acknowledge it on the PR so the trail shows what was addressed. How you reply depends on whether the item is an inline thread or a top-level comment.
When to reply (both cases):
highandmediumitems — whether fixed or determined to be false positiveslowitems — whether fixed or declined by the userself_reviewitems — the same as any other human feedbackreview_summaryitems — a brief acknowledgment; there is nothing to fix, but the trail should still show it was read
Inline review-thread comments (items with a thread_id):
- Reply with the
addPullRequestReviewThreadReplyGraphQL mutation (pullRequestReviewThreadId+body). - Then resolve the thread with the
resolveReviewThreadmutation using the samethreadId— a reply alone does not resolve it.
mutation {
resolveReviewThread(input: { threadId: "<thread_id>" }) {
thread {
isResolved
}
}
}To close out several threads in one pass, use scripts/resolve_pr_threads.cjs THREAD_ID [THREAD_ID ...] instead of repeating the mutation. Resolve threads for high/medium items that were fixed or confirmed as false positives, and low items that were fixed or explicitly declined by the user — never where the action is unclear or still pending.
Top-level comments (items WITHOUT a thread_id — review_summary items and top-level PR/issue comments, e.g. a review bot like Claude that posts its findings as one top-level comment):
There is no thread to reply into, so post a new top-level comment — and when the item carries a comment_id, also add a 🎉 reaction to the original. The reaction is the machine-readable record that the item is handled: fetch_pr_feedback.cjs checks whether we reacted and buckets the comment as resolved, so a re-run stops reporting it. Without it, every later pass re-surfaces the same comment and you have to reason about whether you already dealt with it.
gh pr comment <pr> --body "..."
gh api -X POST "repos/{owner}/{repo}/issues/comments/<comment_id>/reactions" -f content=hoorayTake the id from the item's comment_id rather than searching by body text. If you do need to look it up, use the PR-scoped endpoint with --paginate — the repo-wide repos/{owner}/{repo}/issues/comments returns every comment in the repository, and the PR-scoped one defaults to 30 per page, so an unpaginated search silently misses comments on later pages:
gh api "repos/{owner}/{repo}/issues/<pr>/comments" --paginate \
--jq '.[] | select(.id == <comment_id>) | .id'The reaction is scoped to us. The script does not trust the raw reaction count — anyone can react 🎉 to a comment for unrelated reasons, and treating that as handled would silently drop real feedback. It confirms the reaction belongs to the authenticated user, and fails closed: if the viewer cannot be identified, the item resurfaces. Answering twice beats dropping something.
Items without a comment_id cannot be reacted to. GitHub exposes no reactions endpoint for a pull-request review body, so review_summary items — the text of a submitted review, as opposed to an ordinary PR conversation comment — have no reaction target. For those, dedupe by scanning existing top-level comments for one whose reference marker already cites this author and snippet. In practice most review bots post their findings as an ordinary comment, which does carry a comment_id.
A PR can carry several independent top-level comments, so a bare reply is ambiguous — open every top-level reply with a reference marker identifying the comment you are addressing. Cite the author and the opening of the original, and link it when the item includes a url:
> **Re:** @<author> — "<first line of the original, ~100 chars>…"
> <item `url`, if present>
<what changed, or why it's not an issue>
*— AI Coding Agent*Reply format (both cases):
- 1-2 sentences: what was changed, why it's not an issue, or acknowledgment of declined items.
- End every reply with
\n\n*— AI Coding Agent*. - Before replying, dedupe against re-loops: for inline threads, check whether the thread already has a reply ending in
*- AI Coding Agent*/*— AI Coding Agent*. For a top-level comment with acomment_id, the 🎉 reaction handles it — an item carryingacknowledged: true(bucketed asresolved) has already been answered, so skip it. For areview_summarywith nocomment_id, fall back to the reference-marker scan. - If the
gh/GraphQL call fails, log and continue — do not block the workflow.
4. Check CI Status
Run ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.cjs to get structured failure data.
Wait if pending: If review bot checks (sentry, warden, cursor, bugbot, seer, codeql) are still running, wait before proceeding—they post actionable feedback that must be evaluated. Informational bots (codecov) are not worth waiting for.
This applies even when the check itself has already concluded. A review bot can report its check success while its comment is still the placeholder it posted on trigger — check summary.review_in_progress from fetch_pr_feedback.cjs (step 2), not the body text or the check's conclusion, and wait for it to drop to 0 before treating that bot's feedback as final.
The OpenSpec archive signal is a label and a warning, not a check
openspec-label.yml runs on every pull request with no branches: filter. It
reads the head tree and asks one question: does any directory other than
archive/ exist under openspec/changes/? If one does, the PR gets the
Open OpenSpec label. The predicate asks nothing about stack position, so
every PR in a stack gets the same answer, and the label clears when a branch in
the stack archives the change.
The tip gets more. After labelling, the workflow runs
gh pr list --state open --base <this branch>; a count of zero means nothing is
stacked above, so this is the last branch that can archive before the change
reaches main. It then emits a ::warning:: annotation and a job-summary block
listing one pnpm openspec archive <change> line per change. That is an
annotation and a summary, not a PR comment, so read the OpenSpec Label job
rather than waiting for something to appear in the conversation. A fork PR gets
neither the label nor the tip warning: the token is read-only and the fork's
head branch cannot be a base here, which would report every fork PR as the tip.
On a tip PR, treat that warning as feedback to act on before merge. Sync the
deltas into openspec/specs/, archive, push. "Archive on landing" is not an
option, because main only takes PRs: a change that lands unarchived needs a
second PR to correct it. On taskless/marketing, #50 and #51 landed unarchived
and the fix could not be pushed directly, since the main ruleset has no bypass
actors, so #52 was a third PR to do what the tip should have done.
On a non-tip PR, leave the change directory alone and let the label stand.
Archiving mid-stack rewrites openspec/specs/ before the work it describes has
landed.
Nothing fails either way, and that is deliberate: a gate here was expected-red
for as long as a forward-merging stack took to drain, and a signal expected to
be red is not a signal. The channel is the label and an issue. Once no open PR's
diff touches the change directory, the next push to main has
openspec-tracking.yml open OpenSpec: <change> is unarchived on main, labelled
Open OpenSpec, which closes itself when the change reaches
openspec/changes/archive/. openspec-sweep.yml runs on a daily cron but
escalates on that issue at most once per seven-day window: it acts only once the
directory has gone seven days without git activity, and then only if the issue
itself has been idle that long, so any comment on the thread defers the next
escalation by another window.
Stacked PRs: two other check behaviours worth knowing
Changeset warns, it does not fail. It looks for a .changeset/*.md added
or modified anywhere between main and this PR's head, which covers every
branch below it in the stack. A warning means no changeset exists anywhere in
the stack. If the change ships user-visible behaviour, add one on the BOTTOM
branch rather than labelling anything skip-changeset; every branch above
inherits it, since a child contains its ancestors' commits. Extend that one file
as later PRs land; never add a second changeset per PR.
on: pull_request: branches: [main] tells you nothing dependable about where
a workflow runs. GitHub sometimes resolves a stacked PR's eventual target and
matches on that, so such a workflow may run on mid-stack PRs — and may also
silently stop. Measured on the #71→#106 stack: every PR up to #102 got a
Validate run, #103 and #106 got none, across 16 pull_request events that
filter-less workflows handled fine. #103 reached "ready for review" as a
~93-file change never linted, typechecked, or tested in CI.
So: a workflow that must run everywhere carries no branches: filter (and
names ready_for_review in types:, which is not in the default set). A
workflow that must act only on the PR merging to main establishes that from
the base ref or its stack position, inside the job. Either way, when judging
whether a check "should even be running here," check the stack — and if a check
you expected is simply absent, suspect the filter before assuming the PR is
fine. An absent check reads like a passing one.
Never leave a PR on red
No PR merges with failing tests, including mid-stack. "Merging down" changes where a PR merges, not whether its tests pass — and a red branch you merge into its parent carries that red upward.
It is tempting on a stack to explain a failure as structural: this unit relocates something and the unit that consumes it lands next, so of course it fails. Treat that explanation as a bug report about the split, not a reason to proceed. If unit N alone leaves the suite red, unit N is incomplete — the change that makes it self-consistent belongs in it. In practice that means the PR that moves something also repoints every reader and updates the tests that assert the old shape; the next PR then generalizes.
This is not hypothetical. A stack here sat on 20 failures rationalized as "dispatch arrives in the next PR." The real causes were a migration that relocated files while every reader kept the old paths, and a command that discovered rules before running the migration that creates them. Both were real defects, in the released code path, that the structural story explained away. An empty scan reports success, so the symptom was "no findings" rather than an error — which is exactly why it looked like an artifact of the split.
So: never annotate a red check as expected and move on. Check the branch out, run the suite, and fix the unit until it is green on its own. The only failures that survive that treatment are ones you can point at a specific missing commit for — and if that commit is in this stack, it belongs in this PR.
5. Fix CI Failures
For each failure in the script output:
- Read the
log_snippetand trace backwards from the error to understand WHY it failed — not just what failed - Read the relevant code and check for related issues (e.g., if a type error in one call site, check other call sites)
- Fix the root cause with minimal, targeted changes
- Find existing tests for the affected code and run them. If the fix introduces behavior not covered by existing tests, extend them to cover it (add a test case, not a whole new test file)
Do NOT assume what failed based on check name alone—always read the logs. Do NOT "quick fix and hope" — understand the failure thoroughly before changing code.
6. Verify Locally, Then Commit and Push
Before committing, verify your fixes locally:
- If you fixed a test failure: re-run that specific test locally
- If you fixed a lint/type error: re-run the linter or type checker on affected files
- For any code fix: run existing tests covering the changed code
If local verification fails, fix before proceeding — do not push known-broken code.
git add <files>
git commit -m "fix: <descriptive message>"
git push7. Monitor CI and Address Feedback
Poll CI status and review feedback in a loop instead of blocking:
- Run
node ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_checks.cjsto get current CI status - If all checks passed → proceed to exit conditions
- If any checks failed (none pending) → return to step 5
- If checks are still pending:
a. Run
node ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.cjsfor new review feedback b. Address any new high/medium feedback immediately (same as step 3) c. If changes were needed, commit and push (this restarts CI), then continue polling d. Sleep 30 seconds, then repeat from sub-step 1 - After all checks pass, do a final feedback check:
sleep 10, then runnode ${CLAUDE_SKILL_ROOT}/scripts/fetch_pr_feedback.cjs. Address any new high/medium feedback — if changes are needed, return to step 6.
8. Repeat
If step 7 required code changes (from new feedback after CI passed), return to step 2 for a fresh cycle. CI failures during monitoring are already handled within step 7's polling loop.
Exit Conditions
Before exiting, check summary.review_in_progress. If it is > 0, do not exit — a review bot's placeholder is not a finished review, and needs_attention: 0 alongside it means "hasn't started," not "clean." Sleep 30 seconds and re-check feedback, addressing any new high/medium feedback that lands as it finishes (return to step 3).
This loop has a bound, and reaching it is a report rather than a retry. Give up after roughly ten minutes of a count that never drops, and tell the user the review appears stuck, naming the PR and the bot. A placeholder can be left behind permanently: the run can be cancelled, or its job can crash, and the comment then sits in its posted state with nobody to edit it. Waiting forever on that is the same failure as exiting early, arrived at from the other side, and it is an infrastructure problem under Ask for help rather than something more polling will fix. Re-triggering the review is usually the fix, but that is the user's call, not yours.
Then check summary.pending_reviewers. If it is > 0, reviewers have been requested but haven't submitted yet — their review may produce new feedback. Ask the user whether to wait:
- Yes: sleep 30 seconds, re-check feedback. If new high/medium feedback appeared, address it (return to step 3). If
pending_reviewersdropped to 0, proceed to exit. Repeat until reviewers complete. - No: proceed to the exit conditions below.
If waiting produced code changes, return to step 2 for a fresh cycle.
Success: All checks pass, post-CI feedback re-check is clean (no new unaddressed high/medium feedback including review bot findings, and no review still in progress), user has decided on low-priority items, and pending reviewers resolved or user opted to skip.
Ask for help: Same failure after 2 attempts, feedback needs clarification, infrastructure issues.
Stop: No PR exists, branch needs rebase.
Fallback
If scripts fail, use gh CLI directly:
gh pr checks name,state,bucket,linkgh run view <run-id> --log-failedgh api repos/{owner}/{repo}/pulls/{number}/comments
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

