Repository navigation
Tick /ship-it's CI item from the head commit's status rollup - #176
Conversation
609f3a6 to
07849f2
Compare
Step 5 ticked the pull request's CI test-plan item from `gh pr checks`, whose output names no commit, so a green result could not be tied to the head Step 4 had just pushed. That push has usually only just started the checks, so the item was either ticked from a stale result or left open with nothing to bring the run back to it. Step 5 now reads `gh pr view --json state,headRefOid,statusCheckRollup` for an open pull request, leaving out the workflow's own `ship-it` status, beside local HEAD and the pushed branch tip. The item is ticked only when those three name one commit, the rollup lists at least one check and every check reports `SUCCESS`, and it names that commit. A push updates the pull request's head before its checks register, so an empty list counts as pending, like a running check. A `CANCELLED` check leaves the item open and is put to the user, since GitHub Actions reports a superseded run that way rather than as a failure, and a tick left from an earlier commit is reopened. Step 7 reads the rollup again before asking to mark the pull request ready, ticks the item if the checks now pass, states the CI result in that question and says in the final report whether the item was ticked and on which commit. Fixes #175.
36630e8 to
1174585
Compare
Document Review: /ship-it SKILL.md (
|
| Scope | .claude/skills/ship-it/SKILL.md, the changes on this branch since base commit ea056b8 |
| Branch | tick-ship-it-ci-from-head-rollup |
| Reviewed | 2026-10-24 |
| Reviewed by | Opus 5.5 (claude-opus-5-5) |
| Collated by | Opus 5.5 (claude-opus-5-5) |
Documents
| Document | Commit |
|---|---|
.claude/skills/ship-it/SKILL.md |
07849f2 |
Review History
2026-10-24 — Initial review by Opus 5.5 (claude-opus-5-5)
Overall Assessment
The change does what it sets out to do in intent, and the reviewer ran its jq filter against the pull request and saw it print the head commit and its checks with ship-it excluded, but the condition for ticking the CI item has three gaps that can still produce a wrong status claim. An empty rollup satisfies "every check listed has succeeded" (F1, High, confirmed by the orchestrator right after this branch's push), the canceled-run test can't be applied from the block's output and describes CircleCI rather than this repository's GitHub Actions (F2), and the tick skips the branch-tip check the user-wide rule requires (F3); together with the re-run case (F6) these form G1, the Step 5 tick paragraph and its block. G2 covers Step 7: the CI re-read comes after the irreversible mark-ready question (F4), and the final report's contents don't include the CI state the new paragraph sends there (F5). No ⚖️ Decision is open; three 💡 observations offer optional wording and filter tweaks. All six actionable findings are fixed and both groups are closed, and the three observations were applied in the same edits.
Accuracy
F1 🟠 High Priority - An empty rollup meets "every check listed … has succeeded" ✅ Fixed
-
Document —
.claude/skills/ship-it/SKILL.md -
Location — Step 5: Write the Pull Request, the paragraph after the rollup block ("Tick the CI item only when
headRefOidequals localHEADand every check listed for it has succeeded…"). The Step 7 paragraph relies on the same rule. -
Issue — The tick condition can be met with no checks at all. Step 4's push updates the pull request's
headRefOidright away, but the workflow takes a few seconds to queue itslintcheck run, so.statusCheckRollupcan be empty whileheadRefOidalready equalsHEAD. "Every check listed has succeeded" is then true of an empty list. Nothing is pending either, so the "while any is pending" clause doesn't keep the item open. The agent would tick the CI item in a public pull request body before CI has started.The text also never says what counts as success. A check run reports
conclusion, a status context reportsstate, and values such asSKIPPED,NEUTRALandCANCELLEDare left to judgment. This is the kind of wrong status claim the change exists to prevent. -
Suggestion — Reword the condition to: "Tick the CI item only when
headRefOidequals localHEAD, the rollup lists at least one check and every check listed reportsSUCCESS, and name that commit's short SHA in the item. An empty list means the checks have not registered yet: treat it like a pending check." Step 7's "If the checks have since succeeded" then inherits the rule. -
Recommendation — Implement: it's a one-sentence change that closes the most likely false tick, the one right after Step 4's push.
-
Verification — Observed by the orchestrator: immediately after this branch's force push in this session,
gh pr view 176 --json headRefOid,statusCheckRollupreturnedheadRefOid07849f2 (equal to localHEAD) with an emptystatusCheckRollup, confirming the empty-rollup window. -
Status — ✅ Fixed: the tick now requires the rollup to list at least one check and every check listed to report
SUCCESS, and treats an empty list as pending
F2 🟡 Medium Priority - The canceled-run test can't be applied here and doesn't fit this repository's CI ✅ Fixed
-
Document —
.claude/skills/ship-it/SKILL.md -
Location — Step 5, same paragraph, last sentence ("A check far shorter than usual that reports a failure may be a run canceled on purpose; ask rather than tick or call it red.")
-
Issue — The sentence has two problems:
- No timing in the output. The rollup block prints only name, status and conclusion or state. It prints no
startedAtorcompletedAt, so the agent can't tell whether a check was "far shorter than usual". - Wrong platform. The heuristic comes from the user-wide CLAUDE.md, whose wording is about CircleCI: "A CircleCI run canceled on purpose reports as a failed status". This repository runs GitHub Actions (
.github/workflows/ci.yml, withconcurrency: cancel-in-progress: true). There a canceled run reports the conclusioncancelled, not a failure. I verified this withgh run list --repo mattmenefee/dotfiles --status cancelled --limit 3 --json conclusion,…, where each run shows"conclusion":"cancelled".
In the rollup a cancellation therefore shows as
CANCELLEDand is never confused withFAILURE. - No timing in the output. The rollup block prints only name, status and conclusion or state. It prints no
-
Suggestion — Replace the sentence with: "A
CANCELLEDcheck was superseded by a later push or stopped by hand: it is neither a pass nor a failure, so leave the item open and ask rather than tick it or call it red." -
Recommendation — Implement: the current test can't be carried out from the block, and it describes a failure mode this CI doesn't have.
-
Status — ✅ Fixed: replaced the shorter-than-usual test with the suggested
CANCELLEDrule
F3 🟡 Medium Priority - The tick doesn't check the pushed branch tip, which the user-wide rule requires ✅ Fixed
-
Document —
.claude/skills/ship-it/SKILL.md -
Location — Step 5, rollup block and the paragraph after it. Step 7's paragraph reuses them.
-
Issue — The user-wide CLAUDE.md, under Pull Requests, says: "Before calling a branch green or merging it, check that
git ls-remote origin refs/heads/<branch>equals localHEADand the rollup's commit." Ticking the CI item is calling the branch green.The new block compares only
headRefOidwithHEAD. If another session pushes after this one, GitHub'sheadRefOidcan briefly lag behind the branch. ThenheadRefOidstill equals the localHEADwhile the branch tip has moved, and the item is ticked for a commit that is no longer the head. Step 7's record block already checks all three values (head,remote,pr_head), so the CI tick is the only status claim in the skill that skips the branch tip. -
Suggestion — Add
git ls-remote origin 'refs/heads/<branch>' | cut -f1to the rollup block aftergit rev-parse HEAD, matching the line in Step 7's record block. Change the condition to "when localHEAD, the pushed branch andheadRefOidall name one commit". -
Recommendation — Implement: it's one line, it matches the user-wide rule and Step 7's own block, and it closes the race.
-
Status — ✅ Fixed: the rollup block prints the pushed branch tip with
git ls-remote, and the tick requires localHEAD, the pushed branch andheadRefOidto name one commit
Clarity and Structure
F4 🟢 Low Priority - Step 7 re-reads CI only after the mark-ready question ✅ Fixed
- Document —
.claude/skills/ship-it/SKILL.md - Location — Step 7: Mark Ready and Record the Run, the new paragraph ("When Step 5 left the CI item open, read the rollup with its block again…")
- Issue — The paragraph comes after the
AskUserQuestionabout marking the pull request ready. Marking ready notifies watchers and can't be undone. As written, the user rules on that without the CI state the skill is about to read. The pull request could go out of draft with red or pending CI, and the user would learn this only from the final report. - Suggestion — Move the paragraph to just before "On every run, name each deferred finding…", and include the CI state (passed on which commit, pending or failed) in the mark-ready question.
- Recommendation — Implement: it's a paragraph move that puts the CI state in front of the user before a step that can't be undone.
- Status — ✅ Fixed: the CI re-read now comes before the mark-ready question, which states the CI state just read
F5 🟢 Low Priority - The final report doesn't include the CI state that the new paragraph sends there ✅ Fixed
- Document —
.claude/skills/ship-it/SKILL.md - Location — Step 7, the closing paragraph ("Finish by telling the user what was pushed and how, …"), compared with the new paragraph's "say so in the final report"
- Issue — The new paragraph points a pending or failed CI result to "the final report". The paragraph that defines the report lists what was pushed, which files were posted, deleted or kept, the
ship-itstatus and the URL, but not CI. An agent following the closing list literally can leave CI out. - Suggestion — In the closing paragraph, add "whether the CI item was ticked and on which commit, or what kept it open" after "…what withheld it".
- Recommendation — Implement: it's one clause, and it keeps the two paragraphs from disagreeing.
- Status — ✅ Fixed: the closing paragraph now reports whether the CI item was ticked and on which commit, or what kept it open
F6 🟢 Low Priority - On a re-run, a carried-over body can keep a CI tick for an older head ✅ Fixed
- Document —
.claude/skills/ship-it/SKILL.md - Location — Step 5, the CI tick rule and the paragraph "When an open pull request already has a body, carry over anything in it that was not derived from the commits…"
- Issue — After an earlier
/ship-itrun, the existing body may hold a ticked CI item naming the previous head's short SHA. On a re-run after a new push, the skill composes from the commits but also edits an existing body. Nothing says an earlier CI tick must be reopened when its SHA is no longerHEAD. The tick rule implies it, but an agent revising the existing body can keep the old tick as it stands. - Suggestion — Append to the tick paragraph: "A CI item already ticked for an earlier commit is reopened until the rollup passes on this one."
- Recommendation — Implement: it's one sentence, and re-runs after a push are the normal case for this skill.
- Status — ✅ Fixed: added the suggested reopen sentence to the tick paragraph
Observations
F7 💡 Observation (optional action) - "status rollup, which names the commit" is slightly loose
- Document —
.claude/skills/ship-it/SKILL.md - Location — Step 5, "Read CI for an open pull request from its status rollup, which names the commit it reports on;
gh pr checksdoes not:" - Issue —
statusCheckRolluphas no SHA field. The commit is named byheadRefOid, which the same query returns. A reader looking for a SHA inside the rollup entries won't find one. Optional fix: "fromgh pr view, whoseheadRefOidnames the commit its status rollup reports on;gh pr checksnames none:". Applied: the lead-in now saysheadRefOidnames the commit the status rollup reports on and thatgh pr checksnames none.
F8 💡 Observation (optional action) - The rollup block lacks the OPEN filter the skill's other gh pr view reads use
- Document —
.claude/skills/ship-it/SKILL.md - Location — Step 5, rollup block
- Issue — Step 5's check-and-send block and Step 7's record block both use
select(.state == "OPEN"). Step 5 notes that gh returns a closed or merged pull request for a reused branch name. The rollup block relies only on the prose gate "for an open pull request". Optional fix: addstateto--jsonand wrap the filter inselect(.state == "OPEN") | …, so a closed pull request prints nothing. Applied: the block readsstateand wraps the filter inselect(.state == "OPEN"); run against merged pull request 173, it prints nothing.
F9 💡 Observation (optional action) - "with its block" is ambiguous
- Document —
.claude/skills/ship-it/SKILL.md - Location — Step 7, "read the rollup with its block again"
- Issue — "Its" refers back to the rollup, and the block it means is in another step. Optional fix: "read the rollup again with Step 5's rollup block". This matches the next sentence's "Step 5's check-and-send block". Applied: Step 7 now reads "read the rollup again with Step 5's rollup block".
Consolidated Summary
| Finding | Type | Category | Description | Document | Location | Recommendation | Group | Status |
|---|---|---|---|---|---|---|---|---|
| F1 | 🟠 High | Accuracy | An empty rollup meets "every check listed … has succeeded" | .claude/skills/ship-it/SKILL.md |
Step 5, tick paragraph | Implement | G1 | ✅ |
| F2 | 🟡 Medium | Accuracy | Canceled-run test can't be applied and doesn't fit this repository's CI | .claude/skills/ship-it/SKILL.md |
Step 5, tick paragraph | Implement | G1 | ✅ |
| F3 | 🟡 Medium | Accuracy | Tick doesn't check the pushed branch tip | .claude/skills/ship-it/SKILL.md |
Step 5, rollup block | Implement | G1 | ✅ |
| F4 | 🟢 Low | Clarity and Structure | Step 7 re-reads CI only after the mark-ready question | .claude/skills/ship-it/SKILL.md |
Step 7, CI paragraph | Implement | G2 | ✅ |
| F5 | 🟢 Low | Clarity and Structure | Final report omits the CI state sent there | .claude/skills/ship-it/SKILL.md |
Step 7, closing paragraph | Implement | G2 | ✅ |
| F6 | 🟢 Low | Clarity and Structure | Re-run can keep a CI tick for an older head | .claude/skills/ship-it/SKILL.md |
Step 5, tick paragraph | Implement | G1 | ✅ |
| F7 | 💡 Observation | Accuracy | "status rollup, which names the commit" is slightly loose | .claude/skills/ship-it/SKILL.md |
Step 5, rollup lead-in | — | — | — |
| F8 | 💡 Observation | Clarity and Structure | Rollup block lacks the OPEN filter |
.claude/skills/ship-it/SKILL.md |
Step 5, rollup block | — | — | — |
| F9 | 💡 Observation | Clarity and Structure | "with its block" is ambiguous | .claude/skills/ship-it/SKILL.md |
Step 7, CI paragraph | — | — | — |
Pre-Merge Checklist
G1 ✅ — Tighten the CI tick condition in Step 5
Closed: every member was fixed in Step 5's rollup block and the tick paragraph after it.
- ✅ F1 - Require at least one check and every check
SUCCESS; treat an empty rollup as pending - ✅ F2 - Replace the shorter-than-usual test with a
CANCELLEDrule - ✅ F3 - Add the
git ls-remotebranch-tip line and require all three commits to match - ✅ F6 - Reopen a CI item ticked for an earlier commit
G2 ✅ — Surface the CI state in Step 7
Closed: both members were fixed in Step 7, which now puts the CI state in the mark-ready question and the final report.
- ✅ F4 - Move the CI re-read ahead of the mark-ready question and include its state there
- ✅ F5 - Add the CI item's state to the final report's contents
Summary
/ship-itStep 5 now ticks the CI test-plan item fromgh pr view --json state,headRefOid,statusCheckRollupinstead ofgh pr checks, whose output names no commitHEAD, the pushed branch and the pull request's head name one commit, the rollup lists at least one check and every check reportsSUCCESS(the workflow's ownship-itstatus left out), and it names that commit's short SHACANCELLEDcheck leaves the item open and is put to the user; a tick left from an earlier commit is reopenedFixes #175
Test plan
HEAD, the pushed branch andheadRefOidprinted the same commit, followed by every lint check--jqfilter against merged PR Add a /rebase skill that rebases, verifies and pushes under a lease #173: it printed nothingheadRefOidnamed the new commit while the rollup listed no checksgh pr checksoutput carries no commit SHA (run against PR Find unfilled blocks for /reflow and run it from /ship-it #171)/reflowfinds no unfilled block among the changed lines/ship-iton this branch end to end with the new Step 5 and Step 7 wording: Step 5 left the CI item open while the rollup showedlint QUEUED, and Step 7 re-read it, stated it in the mark-ready question and ticked it once every check succeeded