Skip to content

feat: rescue and suggest modes for the upgrade pipeline - #22

Merged
george-elphick-talieisin merged 4 commits into
mainfrom
feat/rescue-and-suggest-modes
Sep 6, 2026
Merged

george-elphick-talieisin merged 4 commits into
mainfrom
feat/rescue-and-suggest-modes

Conversation

@george-elphick-talieisin

Copy link
Copy Markdown
Contributor

Summary

  • The upgrade pipeline gains three server-derived modes (never label-derived): upgrade (classic majors), rescue (non-majors with red head CI and a positively green base — tri-state in-gate verification incl. paginated legacy statuses, auto-merge verifiably disarmed at queue AND before both push rounds, no_changes_needed invalid, completion never re-arms auto-merge, ci-watch requires the originally failing checks green on a verified live head), and suggest (GitHub-Actions majors: changelog research posted as tier-tagged suggestion blocks in a COMMENT review — the agent never pushes workflow files; deterministic validation covers paths, anchors, spans, sizes, and fence-breakout in both replacement and rationale; delivery is proven or the PR parks).
  • auto-merge.yml: AI-lifecycle guard (a rescue push can never re-approve/re-arm on synchronize); actions majors now queue for suggest instead of dead-ending in a comment.
  • Head movement anywhere (gate, suggest posting, final labeling) requeues or fails closed — an unvalidated SHA is never adopted; terminal labels are mutually exclusive on every transition including the crash finalizer.
  • Codex-reviewed through 4 rounds (plan + implementation + 2 verification passes); all findings closed.

Test plan

  • macos-intune#143 re-queued after merge → suggest round posts verdicts + suggestions
  • A red-CI non-major (e.g. macwhisper#60) rescued end-to-end with human merge
  • Non-major auto-merge still works on ai-label-free PRs

Gate derives mode server-side (never from labels): upgrade (classic
majors), suggest (github-actions majors - research + suggestion blocks,
the agent NEVER pushes workflow files), rescue (non-majors with red head
CI and a positively green base, tri-state comparison incl. legacy
statuses, auto-merge verifiably disarmed).
auto-merge.yml: AI-lifecycle guard so a rescue push (synchronize) can
never re-approve/re-arm; actions MAJORS now queue for suggest.
Rescue: deterministic failing-log prefetch (agent never needs gh),
no_changes_needed invalid, disarm-verify recheck at push time,
completion never re-arms auto-merge - a human merges.
Suggest: COMMENT review with tier-tagged suggestion blocks, validated
deterministically (paths, spans, sizes, fence-breakout), plain-comment
fallback when anchors are rejected; terminal label ai-suggested.

Claude-Session: https://claude.ai/code/session_01W2sTkntuVGqxtoZcANZjSL
H1: gate + caller gain statuses:read; snapshot uses the paginated raw
statuses endpoint (combined truncates at 30).
H2: the gate never adopts an unvalidated head - movement requeues.
H3: ci-watch includes legacy statuses, requires the originally failing
keys green on the final head (rescue), and never labels a head it did
not verify.
M6: round 2 is disarm-verified for rescue and its prompt is mode-aware.
M7: fence-breakout validation covers rationale (plus length cap).
M8: suggest mode requeues on head movement and parks when no delivery
path succeeded - ai-suggested always means something was posted.
M9: terminal labels are mutually exclusive on every transition.
L10: ci_diagnosis rendered in the sticky summary.

Claude-Session: https://claude.ai/code/session_01W2sTkntuVGqxtoZcANZjSL
Fail closed on an unreadable live head; ci-watch rejects push2=error
(a refused round-2 push must not validate round 1 as complete); moved
suggest runs requeue instead of parking; terminal labels cleared on
every transition incl. blocked/suggested; ci_diagnosis required in the
schema; final check-run listing paginated.

Claude-Session: https://claude.ai/code/session_01W2sTkntuVGqxtoZcANZjSL
Copilot AI lite review requested due to automatic review settings September 4, 2026 13:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new rescue check/status comparison logic has jq grouping/deduplication issues that can produce incorrect “latest check” results, impacting rescue gating correctness.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR extends the Dependabot upgrade automation pipeline with three server-derived execution modes—upgrade, rescue, and suggest—and updates the auto-merge workflow to respect the AI lifecycle so that rescue/suggest flows cannot accidentally self-approve or auto-merge.

Changes:

  • Add rescue mode gating: compare head vs base checks/statuses (including legacy commit statuses) and enforce “auto-merge must be disarmed” before any rescue push.
  • Add suggest mode for GitHub Actions majors: post validated suggestion blocks as a COMMENT review instead of pushing workflow changes.
  • Add head-movement detection and fail-closed requeue/parking behavior when the validated SHA no longer matches live head.
File summaries
File Description
.github/workflows/dependabot-upgrade.yml Implements mode selection (upgrade/rescue/suggest), rescue check/status comparison, suggest-mode review posting, and stronger head-movement / auto-merge-disarm invariants.
.github/workflows/auto-merge.yml Adds an AI-lifecycle label guard to prevent approvals/auto-merge when an AI pipeline state is active; queues majors (including actions majors) appropriately.
Review details

Suppressed comments (1)

.github/workflows/dependabot-upgrade.yml:1478

  • In ci-watch rescue verification, keymap is built with from_entries directly over all check-runs. A commit can have multiple check-runs with the same (app id, name) due to re-runs; without de-duplicating to the latest completed attempt, the map can pick an arbitrary run and incorrectly mark a rescued check as not green (or green) even when the latest attempt differs.
            keymap=$(jq -n --argjson r "$runs" --argjson s "$sts" '
              (($r | map({key: (((.app.id // -1)|tostring) + ":" + .name), value: (.conclusion // "unknown")}))
               + ($s | map({key: ("-1:" + .context), value: (if .state == "success" then "success" else "failure" end)})))
              | from_entries')
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/dependabot-upgrade.yml
@george-elphick-talieisin
george-elphick-talieisin merged commit ac9d83f into main Sep 6, 2026
1 check passed
@george-elphick-talieisin
george-elphick-talieisin deleted the feat/rescue-and-suggest-modes branch September 6, 2026 10:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The rescue and suggest additions include failure-handling paths that can hard-fail or misbehave under API/malformed-output conditions, and should be made deterministically fail-closed as noted in inline comments.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment on lines +169 to +172
armed=$(gh api "repos/$BASE_REPO/pulls/$PR_NUMBER" --jq '.auto_merge != null' || echo true)
[ "$armed" = "false" ] || block "auto-merge is (or may be) armed on this PR — rescue requires it verifiably disarmed first"
own_suite=$(gh api "repos/$BASE_REPO/actions/runs/$GITHUB_RUN_ID" --jq .check_suite_id || echo 0)
snapshot() { # $1 = sha -> JSON {pending: bool, checks: {"app:name": conclusion}}
Comment on lines +748 to +758
bad=$(jq -r --argjson files "$files" '[(.suggestions // [])[] | . as $sg
| select(
(($sg.path | test("^\\.github/(workflows/[^/]+\\.ya?ml|actions/[^/]+/action\\.ya?ml)$")) | not)
or (($files | index($sg.path)) == null)
or ($sg.start_line < 1) or ($sg.end_line < $sg.start_line)
or (($sg.end_line - $sg.start_line) > 60)
or (($sg.replacement | length) > 6000)
or ($sg.replacement | test("```|~~~"))
or (($sg.rationale | length) > 400)
or ($sg.rationale | test("```|~~~")))
| $sg.path + ":" + ($sg.start_line|tostring)] | join(" ")' "$S")
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