ADR-061: Dependabot Version-Pin Auto-Sync
Give Dependabot PRs a pre-synced version:check and auto-heal the versions.json/versions.yml pins on main after manifest-changing merges, so dependency bots stop failing a gate they can never satisfy
Last updated
Status
Accepted — extends ADR-059 (which made versions.json a
cross-repo contract consumed by the docs site’s drift gate)
Context
version:check (in quality:ci) fails the build when the exact pins in versions.json
/ versions.yml drift from package.json. The write side is pnpm versions:sync, which a
human runs before committing a dependency change.
Dependabot cannot run repo scripts. Every Dependabot PR that bumps a pinned dependency
(astro, @playwright/test, @biomejs/biome, …) therefore fails version:check by
construction — the bot bumps package.json and can never commit the synced pins. In
practice (PRs #41, #44) every grouped-minor wave arrived red and had to be replicated by
hand on a maintainer branch. A gate that a legitimate, wanted change can never pass is not
enforcing anything; it is routing bot work to humans.
Decision Drivers
- Enforcement must stay real for humans: a maintainer who edits
package.jsonand forgetsversions:syncshould still fail CI (ADR-039) versions.jsononmainmust stay accurate: the docs repository’s drift gate fetches it raw frommain(ADR-059)- No unreviewable automation against PR branches:
pull_request_target+ bot-triggered pushes is a known injection foot-gun, and commits pushed withGITHUB_TOKENdo not re-trigger CI, leaving the PR’s head commit unvalidated
Considered Options
Option 1: Auto-commit the sync to the Dependabot PR branch
A pull_request_target workflow checks out the bot branch, runs versions:sync, and
pushes the fix-up commit.
- Pros: the merged commit is complete in itself; no follow-up commit on
main - Cons:
GITHUB_TOKENpushes don’t re-triggerpull_requestworkflows, so the synced head commit would carry no green CI;pull_request_targetwith a head checkout widens the attack surface; rejected
Option 2: Pre-sync in CI for bot PRs + auto-heal on main (chosen)
Two small pieces:
ci.ymlrunspnpm versions:syncbeforequality:cionly whengithub.actor == 'dependabot[bot]'— the gate then validates the synced state, which is the only state the bot could ever be asked to produce. For human actors nothing changes; the gate stays strict.- A
versions-sync.ymlworkflow onpushtomain(paths:package.json) runsversions:syncand, if the pins drifted, commits and pushes the regeneratedversions.json/versions.yml.
- Pros: bot PRs judged on what they can control; human enforcement unchanged;
mainconverges to accurate pins within one CI run of any merge; no PR-branch automation - Cons: a Dependabot merge briefly (≈1 min) leaves
mainwith drifted pins until the heal commit lands; the heal commit itself is bot-authored history
Option 3: Drop the pins or the gate
- Pros: deletes the problem
- Cons: ADR-059’s docs drift gate consumes
versions.jsonfrommain; without the in-repo gate the manifest rots silently — the exact failure mode ADR-059 retired; rejected
Decision
Option 2. The gate’s meaning becomes: the pins match package.json at every human
commit, and main self-heals after bot merges.
Implementation Details
ci.ymlbuild-test gains aPre-sync version pins (Dependabot PRs)step gated ongithub.actor == 'dependabot[bot]', immediately beforequality:ci- New
.github/workflows/versions-sync.yml:push→main,paths: [package.json],permissions: contents: write; commits asgithub-actions[bot]only whengit diffshows real drift; rebases before push to tolerate racing merges - The heal commit touches only
versions.json/versions.yml, so it cannot re-trigger the workflow (paths filter) even if bot pushes triggered workflows (they don’t)
Consequences
Positive
- Dependabot waves can merge green without a human replicating them
versions.jsononmainstays trustworthy for the ADR-059 cross-repo gate- Human drift is still a hard failure — enforcement, not cadence
Negative
- Sub-minute pin-drift window on
mainafter a bot merge (docs-repo CI compares only at its own build times; acceptable) - Two extra bot commits per Dependabot merge wave in
mainhistory
Validation
- A Dependabot PR bumping a pinned dep passes
version:checkwithout human intervention - Merging it produces an auto-heal commit that restores
versions.jsonaccuracy onmain - A human PR with drifted pins still fails
quality:ci
References
- ADR-039: Halt-on-Violation Enforcement
- ADR-059: Docs Drift Gate Replaces Push-Sync
- Dependabot PRs #41 / #44 (the structurally-failing waves), replicated by hand in #45
Date: 2026-08-12
Participants: Template maintainers
Outcome: Accepted