Skip to content

feat(update-consumers): fork-PR fallback, caller-permissions sync, dry-run - #92

Merged
Sayt-0 merged 1 commit into
mainfrom
feat/update-consumers-fork-fallback
Aug 25, 2026
Merged

feat(update-consumers): fork-PR fallback, caller-permissions sync, dry-run#92
Sayt-0 merged 1 commit into
mainfrom
feat/update-consumers-fork-fallback

Conversation

@Sayt-0

@Sayt-0 Sayt-0 commented Aug 24, 2026

Copy link
Copy Markdown
Member

Summary

update-consumers.yml can now deliver version-bump PRs to consumer repos where the machine user has no write access (fork fallback), and keeps those PRs from breaking callers whose permissions: grants are below what the pinned review-pr.yml requires (issue #72: v2.0.3 raised actions: read to write and broke callers still granting read).

Routing (ported from migrate-consumers.yml)

Route Condition Behavior
direct machine user has push signed commit on a branch, same-repo PR (unchanged)
fork no push, allow_forking fork under the machine user, readiness polling, parent-verification guard against name collisions, gh repo sync --force, signed commit on the fork, cross-repo PR (owner:branch)
skip no push and forking disabled warning + job-summary entry (needs a write grant or manual update)

Caller-permissions sync (new src/sync-caller-permissions)

resolve $VERSION @ $SHA
  -> fetch review-pr.yml at $SHA
  -> per consumer: sed re-pin  ->  sync-caller-permissions  ->  route  ->  signed commit  ->  PR
  • Computes what the target version's review-pr.yml requires from callers, reusing the caller-permissions extractor already used by the release-notes safeguard, so the two tools can never disagree on a release's requirement.
  • Edits the consumer's permissions: block in place, in the same PR: raises insufficient levels, appends missing scopes. Block maps and inline {} maps supported; comments, indentation and CRLF preserved; grants are never reduced. The comparison is absolute (requirement vs granted), so a consumer that was already under-granting gets fixed regardless of the version it comes from.
  • Fail-safe: an unparseable file degrades to a warning and the version bump still goes out. Cases that cannot be edited safely (no explicit permissions: block, read-all shorthand, * pseudo-scope) are surfaced in the consumer PR body as a manual action instead of guessed: inventing a block would zero unlisted scopes, and the repo-default grant is unknowable from outside.
  • CLI stdout protocol, one line per increase: changed|manual <block> <scope> <from> <to>.

New workflow_dispatch inputs

Input Default Purpose
dry-run true prints the route and the diff that would be committed, performs zero writes (not even the fork). Safe default: the workflow is dispatch-only, nothing in release.yml triggers it
repos "" comma-separated allowlist for pilot runs (entries trimmed, matched on the repo field)

A job summary (DRY RUN / EXECUTED) lists PRs opened (with URLs) and skipped repos with reasons.

Fixes that came with the port

Issue Fix
per-iteration trap ... EXIT (traps do not stack, skip paths leaked workdirs) single cleanup_workdir trap + explicit calls on every skip path
--base-ref main hardcoded resolved default branch (also required by the fork sync)
gh pr list --jq '.[0].number' can print the literal string "null" // empty (same fix migrate-consumers documents)
existing-PR lookup does not support owner:branch heads REST pulls?head=owner:branch on the fork route

Validation

Check Result
unit tests (24 new, incl. pins on the real review-pr.yml requirement and the README quick-start caller block) 1043/1043 pass
tsc, biome ci, actionlint clean
harness executing the extracted step script with a fake gh (3 fixture consumers: direct on main, fork on a non-main default branch, skip) dry run: zero writes, routes and diffs reported; execute: signed-commit and pr-create argv correct per route (--base-ref trunk, --head owner:branch); allowlist: whitespace trim and single-repo filtering verified

Suggested first rollout

  1. Dispatch with defaults: dry run over all consumers, review the job summary.
  2. dry-run=false + repos=<one fork-route repo>: validates live fork creation, sync, signed commit on the fork, cross-repo PR.
  3. dry-run=false with no allowlist: full propagation.

…y-run

Open a cross-repo PR from a machine-user fork when the consumer repo
denies write access (same routing as migrate-consumers), and raise the
caller's permissions: grants to what the pinned review-pr.yml requires
(issue #72: actions read -> write) so a version bump can no longer break
callers at startup validation. Add dry-run (default true) and a repos
allowlist for pilot runs, plus a job summary for triage.
@Sayt-0
Sayt-0 enabled auto-merge (squash) August 24, 2026 16:20
@Sayt-0
Sayt-0 merged commit 757e5c9 into main Aug 25, 2026
14 checks passed
@Sayt-0
Sayt-0 deleted the feat/update-consumers-fork-fallback branch August 25, 2026 08:20
Sayt-0 added a commit that referenced this pull request Aug 25, 2026
## Summary

The first live dry run of `update-consumers.yml` (run
[32826389044](https://github.com/docker/docker-agent-action/actions/runs/32826389044),
after #92) discovered the action repo itself as a consumer and showed a
diff that would have mangled the workflow's own discovery query. This PR
excludes the action repos from discovery.

| | |
|---|---|
| Cause | the code-search query inside `update-consumers.yml` contains
`docker/docker-agent-action/.github/workflows/review-pr.yml@` as a
literal string, so the repo matches its own search |
| Why latent until now | while the repo still had a `pr-review.yml`
caller, `unique_by(.repo)` picked that file (updated by #86 and earlier
releases); since the rename to `self-review-pr.yml`, which calls the
reusable workflow via a local ref (`uses:
./.github/workflows/review-pr.yml`, no `@`), the only remaining match in
the repo is the query string itself |
| Impact if executed for real | a self-addressed PR rewriting the query
line (quote, filters and line continuation destroyed), breaking the
workflow |
| Handled by | dropping `^docker/(docker-agent-action|cagent-action) `
lines right after discovery, mirroring the migrate-consumers guard;
`cagent-action` is excluded too since it holds a copy of these workflows
|

## Dry-run evidence

```
Processing docker/docker-agent-action (.github/workflows/update-consumers.yml)...
DRY RUN - route: direct; diff that would be committed:
-  -f q='org:docker "docker/docker-agent-action/.github/workflows/review-pr.yml@" language:YAML path:.github/workflows' \
+  -f q='org:docker "docker/docker-agent-action/.github/workflows/review-pr.yml@06e1767 # v2.0.5
```

No writes were performed (dry run). The rest of the run was nominal: 49
repos routed (17 direct / 29 fork / 3 skip), 33 consumers flagged for
the `actions: read` to `write` caller bump.

## Validation

| Check | Result |
|---|---|
| actionlint, `bash -n` on the extracted step script | clean |
| harness with the action repos injected into the fake search results |
self repos never cloned nor processed; direct/fork/skip routing
regression intact in both dry-run and execute modes |
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