Skill: dependency-update-shepherd
Take one open dependency-update branch at a time from red/stale to merged — evidence-based, bot-aware, with hard stop rules. Repo config, CI logs, and MR state are authoritative; never guess.
IMPORTANT — safety rules (apply throughout)
- GitHub →
gh, GitLab →glab; never call forge APIs directly except where a CLI gap is noted below. Missing CLI → stop, ask the user to install it. - Never commit to or push the target branch (
main/master/…). Work only on the dependency-update branch. - Rewritten history is pushed only with
git push --force-with-lease=<branch>:<sha-your-work-is-based-on>— never--force— and re-check the remote head right before pushing: the bot may have pushed meanwhile. - Never hide a failure: no disabling/skipping tests, linting, or checks; no loosening version pins to make CI pass.
- Iteration cap: each push (or CI-verified hypothesis) that ends in a red pipeline consumes 1 of 3 iterations; after the 3rd red, stop and report. Rebase pushes and job reruns don't count but are logged.
- One flaky rerun per MR, total. A test failure that turns green on rerun is still reported as "flaky, unverified" — never silently treated as a pass.
- Conflict resolution is a semantic decision, not a textual one. Unclear → stop and ask.
- Any MR-visible write (comment, description edit) and auto-merge require the Phase-0 opt-in; agent-authored code fixes additionally require the fresh per-MR diff confirmation in the merge gate.
Phase 0 — Scope & policy
- Detect forge (
git remote -v), target branch (gh repo view --json defaultBranchRef/glab repo view), and update bot:renovate.json*/.renovaterc*→ Renovate,.github/dependabot.yml→ Dependabot, else manual branches. - List open candidate PRs/MRs (
gh pr list --json number,title,author,headRefName,mergeable,statusCheckRollup/glab mr list) and authenticate them: the author must be the verified bot identity (app/dependabot,renovate[bot], or the bot account the repo's config names). Anything else — including every "manual" branch — needs explicit per-MR user confirmation; a branch merely namedrenovate/…is not trusted. Confirm the diff touches manifests/lockfiles. - Classify semver impact per bumped package from the diff. Major-equivalent (never auto-merged unless the user names it explicitly): any major bump, any
0.xminor bump, any grouped MR containing one, and any un-classifiable ref (git SHA, Docker tag/digest, Action pin). - Ask via AskUserQuestion, once: which MR(s) to process (single / all one-by-one, major-equivalents last), whether the skill may write to MRs (comments, description edits), and whether it may merge / arm auto-merge at the end (default: no — leave the MR ready and report).
- Read the repo's rules once: CONTRIBUTING, CI workflows + required checks, protection/approval policy, merge method, and the local verify commands (
package.jsonscripts,Makefile, CI steps). These define what "green" and "verified" mean below.
Phase 1 — Freshness first (bot-aware)
Errors on a stale base are noise — make the branch current before touching any failure.
git fetch origin; behind or conflicting?git rev-list --count <branch>..origin/<target>+ the forge's mergeable state.- If stale and no foreign commits have been pushed yet, prefer the bot's own rebase — it keeps the bot maintaining the branch:
- Dependabot:
gh pr comment <n> --body "@dependabot rebase". - Renovate: fetch the MR body, flip
- [ ]→- [x]on the rebase/retry checkbox line only, write it back (gh pr edit <n> --body-file/glab mr update <iid> --description). - Poll
gh pr view <n> --json headRefOid(GitLab:glab mr view <iid>sha) every ~30 s, max ~10 min — self-hosted Renovate may only act on its next scheduled run. No new push → take over manually.
- Dependabot:
- Once you have pushed anything to the branch, bot rebase is permanently forbidden for this MR — both bots force-push and would wipe your commits; rebase manually from then on. A takeover also ends bot maintenance (Dependabot stops updating the branch, Renovate marks it edited) — record this in the report.
- Manual-rebase conflict rules:
- Lockfile: never hand-edit. Take the target branch's lockfile, re-apply only the intended bump with the ecosystem's conservative command (e.g.
npm install --package-lock-only <pkg>@<version>), then diff against the bot's original lockfile: any changed package, version,resolvedURL, orintegrityhash beyond the declared bump(s) → stop and report (supply-chain risk). - Manifest: if the target branch already bumped the same package (grouped/security-update race), keep the higher intended version — or abort and let the bot recreate the MR. Otherwise keep the bump, take everything else from the target.
- Business logic: stop, ask.
- Lockfile: never hand-edit. Take the target branch's lockfile, re-apply only the intended bump with the ecosystem's conservative command (e.g.
- After a manual rebase: verify and push per Phase 4, then continue at Phase 2.
Phase 2 — Identify the first causal failure
- Fetch the pipeline for the branch's latest commit:
gh pr checks <n>,gh run view <id> --log-failed; GitLab:glab ci get --merge-request=<iid>— plain branch-pipeline commands miss detached MR pipelines. Ignore stale runs. All green → jump to the merge gate. - Find the first causal failure, not downstream noise (a failed build fails every later job).
- Read the full failed-job log; extract the exact command, parameters, and environment (versions, OS, env vars) from the workflow definition.
- Classify: code incompatibility with the new version · test failure · lint/type failure · infrastructure/runner problem · missing secret/permission · transient. GitHub quirk: Dependabot-triggered runs get a read-only
GITHUB_TOKENand the separate Dependabot secrets store — a takeover push changes the actor and can make such failures appear or vanish; that's repo config, not branch-fixable. - Transient/infra suspicion (network timeout, runner death, known-flaky test): use the single rerun —
gh run rerun <id> --failed; GitLab:glab ci retry <job-id>per failed job (the argument-less form is interactive and hangs) orglab api -X POST "projects/:fullpath/pipelines/<id>/retry". Green → Phase 5 with the rerun ID. Red again → real failure. - Missing secret / permissions / infra outage → not fixable from here. Stop and report exactly what's missing.
- No identifiable cause in the logs → no blind changes, no blind pushes. Name the missing context, stop.
Phase 3 — Reproduce & fix locally
- Re-run the failing command locally, matching CI as closely as possible (same tool versions, flags, clean install). If it can't run locally (CI-only secrets/services), fall back to hypothesis → minimal fix → let CI verify (a red result consumes an iteration).
- Trace the error to its concrete cause; read the bumped version's changelog/release notes instead of guessing breaking changes.
- Apply the minimal fix that adapts the code to the new version. No drive-by refactoring, nothing unrelated — the diff must stay reviewable as "dependency update + necessary adaptation".
- If the update itself is incompatible (upstream bug, unsupported platform): don't force it. Document the incompatibility on the MR (if Phase-0 allows writes, else in the final report), suggest the latest compatible version or the needed migration, and stop.
Phase 4 — Verify locally, then push
- The originally failing command now passes; affected tests pass; the repo's full verify command (Phase 0.5) passes if one exists.
git diff origin/<target>...HEADcontains only the dependency update plus documented fixes.- Push — applying the force-with-lease rule from the safety block if history was rewritten.
Phase 5 — Watch the run you triggered
- Identify the run for your push SHA or your rerun:
gh run list --commit <sha>; GitLab:glab ci get --merge-request=<iid> --output jsonand assert itsshaequals the pushed commit. An older green run proves nothing. - Watch to completion:
gh run watch <id>/gh pr checks <n> --watch/ poll the glab command above. - Red → back to Phase 2 (consumes an iteration). Green → merge gate.
Merge gate — immediately before merging (earlier answers expire)
- Right MR, right branch — this is the dependency update that was worked on.
- Branch contains the current target-branch tip; new commits landed since the last rebase → back to Phase 1.
- Forge reports mergeable, no conflicts.
- Latest pipeline on the latest commit fully green. (GitLab merge trains: the train pipeline can still fail — arming ≠ merged.)
- Required approvals & policies satisfied — the skill never approves anything; a missing approval is a blocker to report, not to work around.
- Phase-0 merge opt-in given, and this is not an unapproved major-equivalent (Phase 0.3).
- If the skill authored code changes beyond manifest/lockfile: show the final diff via AskUserQuestion and get a fresh yes — the Phase-0 opt-in predates this code and does not cover it.
Any box open → stop and report the blocker.
Phase 6 — Merge & confirm
- Already mergeable now → merge synchronously with the repo's method:
gh pr merge <n> --squash|--merge|--rebase/glab mr merge <iid>. (gh pr merge --autoerrors on an already-clean PR.) - Only waiting on checks → arm auto-merge (
gh pr merge <n> --auto …, requires the repo settingautoMergeAllowed;glab mr merge <iid> --auto-merge) only if branch protection enforces what the gate checked (required checks, up-to-date branch); otherwise keep watching and merge synchronously once clean. - Watch until the merge is confirmed (
gh pr view <n> --json state,mergedAt/glab mr view <iid>). If the status flips back (new conflict, red check, dropped approval) → re-run the merge gate; never continue silently.
Stop state
Whenever stopping with pushed-but-unfinished work (cap reached, blocker hit): state on the MR — or in the report, if writes weren't allowed — that the branch was taken over, is partially fixed, and is no longer bot-maintained; or revert your commits. Never leave this implicit.
Final report (per MR)
| Repo / MR / branch | … |
| Dependency update(s) | pkg old → new, semver classification (Phase 0.3) |
| Rebase | needed? by bot or takeover? conflicts resolved (which, how) |
| CI failure | first causal job + command, classification, reruns used |
| Root cause & fix | … |
| Verification | local commands run · remote run URL + result |
| Merge | merged / auto-merge armed / left ready — confirmed how? |
| Blockers / not verified | everything the skill stopped on or could not check |
State plainly what was not verified — an honest "stopped: approval missing" beats an optimistic "done".