Skip to content

feat(imageauto): pull-request writeback for protected branches - #89

Merged
felixgeelhaar merged 3 commits into
mainfrom
feat/pull-request-writeback
Jul 22, 2026
Merged

feat(imageauto): pull-request writeback for protected branches#89
felixgeelhaar merged 3 commits into
mainfrom
feat/pull-request-writeback

Conversation

@felixgeelhaar

Copy link
Copy Markdown
Contributor

Problem

Image automation deploys by committing the digest bump directly to the tracked branch. On a protected branch (PRs / status checks required) that push is rejected:

GH006: Protected branch update failed — Changes must be made through a pull request.
klarlabs-klarlabs-site=error

The failure is silent: the config shows =error in the reconcile summary and the target just stops updating. There was no way to run rollops against a protected main at all.

This is not hypothetical — rollopsd could not deploy klarlabs.de (nor mnemos, senat-os) for exactly this reason; every bump had been failing to push.

Fix

Add imagePolicy.writeback: pull-request (default stays push — existing configs unchanged):

imagePolicy:
  mode: digest
  allowMutableTags: true
  writeback: pull-request

In PR mode rollopsd never writes the tracked branch. It commits the bump on rollops/image/<config-name>, force-pushes it, and opens (or refreshes) a PR into the tracked branch, enabling GitHub auto-merge so it lands when required checks pass.

The load-bearing semantic

PR mode returns ref="" from Process — it does not deploy this cycle. The bump lives only on the PR, so the cluster never leads Git. The deploy happens through the ordinary reconcile once the PR merges and the branch advances. That keeps a protected branch and the running target consistent.

Details

  • Idempotent — a duplicate-head 422 resolves to the existing PR and reuses it, so polling neither errors nor spawns PRs.
  • Auto-merge is best-effort — a repo with it disabled still gets the PR opened and waits for a human; writeback is unblocked either way, never stuck in error.
  • The git layer refuses to run the PR-commit path on the tracked branch itself.
  • GitHub only for now; ownerRepoFromURL handles https/ssh/git remotes.

Tests

PR API against a stub server (create, reuse-existing, auto-merge enabled/disabled, hard-failure surfaces); an end-to-end imageauto PR-mode run on a real local git repo + stub API asserting no deploy, tracked branch untouched, bump pushed to the PR branch; and push mode proven unchanged. Full module: build + test ./... + golangci-lint all green.

Docs

docs/image-automation.md gains a "Writeback to a protected branch" section; docs/keel-migration.md gets a callout (keel's direct push has no protected-branch equivalent — this is it).

https://claude.ai/code/session_012qFD1UhQT9tiKkMsPAskK7

…e open threads

Cleans up drift in open-threads.md: the 2026-06-14 and 2026-07-17
[OPEN]/[WAITING] markers were superseded by later entries but still read as
live (roady #34, the .pem deletion, tokenFile stripping, PAT decommission).
Adds an authoritative current-state section at the top.

Records the nox investigation: excluding go.sum was CORRECT (the findings
were ~99% versions not in the build), the real bugs were in nox itself, and
all three are now fixed on nox main via #248. Retracts the claim that
rollops had a live GO-2026-5932 — it is scoped to x/crypto/openpgp, which
rollops does not import.

Claude-Session: https://claude.ai/code/session_01QaHY7x5xKyZWP3RVQ4AgpH
Session log, status.md, decisions.md and open-threads.md.

Headline: the task 'fix the 28 repos excluding go.sum' was based on a wrong
note in this memory and would have injected ~5,263 false positives. The
exclusions were correct; the bug was in nox. Pulling it surfaced that every
dependency finding ever produced was medium with an empty summary, so a
critical dependency CVE could never trip the high/critical gate.

Fixed in Nox-HQ/nox#248, released as v1.10.0, shared workflow pinned.
Canary before the pin bump: agent-go 56 gate-failing (42 critical).

Also retracts the earlier claim that rollops has a live GO-2026-5932.

Claude-Session: https://claude.ai/code/session_01QaHY7x5xKyZWP3RVQ4AgpH
Image automation deployed by committing the digest bump directly to the tracked
branch. On a protected branch — one requiring pull requests or status checks —
that push is rejected (GH006), the config goes =error in the reconcile summary,
and the target silently stops updating. There was no way to run rollops against
a protected main at all.

Add `imagePolicy.writeback: pull-request` (default stays `push`, so existing
configs are unchanged). In PR mode rollopsd never writes the tracked branch: it
commits the bump on a deterministic branch (rollops/image/<config-name>),
force-pushes it, and opens — or refreshes — a PR into the tracked branch,
enabling GitHub auto-merge so the change lands the moment required checks pass.

The load-bearing semantic: PR mode returns ref="" from Process, i.e. it does NOT
deploy this cycle. The bump lives only on the PR, so the cluster never leads
Git; the deploy happens through the ordinary reconcile once the PR merges and
the tracked branch advances to carry it. That is what keeps a protected branch
and the running target consistent.

Details:
- Idempotent: a duplicate-head 422 is resolved to the existing PR and reused, so
  re-running each poll cycle neither errors nor spawns PRs. The head branch is
  force-created from the tracked branch each time, so it tracks the current base.
- Auto-merge is best-effort: a repo with auto-merge disabled (or a token lacking
  permission) still gets the PR opened and waits for a human — writeback is
  unblocked either way, never left in an error.
- The git layer refuses to run the PR-commit path on the tracked branch itself.
- GitHub only, for now; ownerRepoFromURL handles https/ssh/git remotes.

Tested: PR API against a stub server (create, reuse-existing, auto-merge
enabled/disabled, hard failure surfaces); an end-to-end imageauto PR-mode run on
a real local git repo + stub API asserting no deploy, tracked branch untouched,
and the bump pushed to the PR branch; push mode proven unchanged.

Motivated by a real incident: rollopsd could not deploy klarlabs.de (and mnemos,
senat-os) because their mains are protected — every image bump had been silently
failing to push.

Claude-Session: https://claude.ai/code/session_012qFD1UhQT9tiKkMsPAskK7
@felixgeelhaar
felixgeelhaar merged commit 5255849 into main Jul 22, 2026
6 checks passed
@felixgeelhaar
felixgeelhaar deleted the feat/pull-request-writeback branch July 22, 2026 12:55
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.

1 participant