From ffbc1afc3d18d2ce56892bfc836c4093ccdfb047 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 24 Aug 2026 23:57:07 +0000 Subject: [PATCH] fix(labels): defer fork-head writes to sweep --- .github/workflows/labels.yml | 48 +++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index a98565c..1625527 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -2,12 +2,14 @@ name: labels # Reusable half of the labels automation. Triggers and permissions live in # the caller; docs/CONSUMERS.md carries the complete caller stub. # -# The caller uses pull_request_target, not pull_request: every PR in this -# family arrives from a fork, where pull_request runs with a READ-ONLY token -# and cannot label anything. _target is safe in this workflow because no PR -# code is ever checked out or executed — scope reads changed paths and the -# path mapping via the API and checks out only the ceremony implementation. -# Keep it that way. +# The caller uses pull_request_target, not pull_request, so same-repository PRs +# keep the base repository's write token without running PR code. On this +# Forgejo, unlike GitHub, fork-headed _target runs still receive a read-only +# token. Those runs therefore attempt no writes and leave labeling to the +# scheduled sweep; the explicit fork_head job below records that disposition +# as a successful check. Both write paths execute only for same-repository +# heads. Scope reads changed paths and the path mapping through the API and +# checks out only the ceremony implementation. Keep it that way (#241). # # The reconcile sweep lived here until #209. Riding the PR-triggered run # meant every displacement in the sweep's shared concurrency queue recorded @@ -21,8 +23,11 @@ name: labels # sweep now lives in labels-sweep.yml behind its own caller, and the # trigger job below is its wake: it fires on every event this caller # subscribes — the exact surface that used to run reconcile directly — so -# the wake latency (#137) is unchanged, while a displaced sweep cancels on -# the Actions tab, attached to no PR. PR checks show scope + trigger only. +# same-repository wake latency (#137) remains seconds-scale, while a displaced +# sweep cancels on the Actions tab, attached to no PR. Fork-headed runs cannot +# dispatch with their read-only token, so their labels arrive on the scheduled +# sweep cadence. PR checks show scope + trigger for same-repository heads, or +# fork_head for fork heads. # # This cannot loop: the trigger's dispatch and the reconciler's label # writes both use GITHUB_TOKEN. GitHub does not create workflow runs from @@ -60,6 +65,7 @@ jobs: # scope run is clobbered. if: >- github.event_name == 'pull_request_target' && + github.event.pull_request.head.repo.full_name == github.repository && github.event.action != 'labeled' && github.event.action != 'unlabeled' && github.event.action != 'review_requested' && @@ -106,15 +112,10 @@ jobs: CONFIG_REF: ${{ github.sha }} trigger: - # The sweep's wake (#209). No `if:`: reconcile carried none, so the - # trigger keeps the whole event surface the caller subscribes — - # workflow_dispatch of the labels caller itself included. That cannot - # double-fire bootstrap: this dispatch always carries bootstrap=no, so - # a dispatched labels caller yields one plain sweep, and the taxonomy - # bootstrap fires solely on a manual dispatch of the sweep caller - # (whose input defaults to "yes"). Excluding workflow_dispatch here - # would instead make a dispatched labels caller do nothing at all — - # a silent no-op run is worse than a redundant sweep. + # The sweep's instant wake (#209) keeps the whole non-PR event surface and + # same-repository PRs. Fork-headed PRs are excluded because this Forgejo + # gives their pull_request_target run a read-only token (#241); fork_head + # records the successful deferral to the scheduled sweep instead. # # LOUD on failure — never `|| true`: a red trigger is the # misconfiguration alarm. A consumer that bumps the pin without adding @@ -122,6 +123,9 @@ jobs: # `bootstrap` input (unexpected input), or without `actions: write` # on this caller (permission denied) fails HERE, visibly on the PR, # instead of silently never sweeping again. + if: >- + github.event_name != 'pull_request_target' || + github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest steps: - name: dispatch the sweep @@ -199,3 +203,13 @@ jobs: exit 1 fi echo "labels: sweep dispatched — $SWEEP_WORKFLOW on $branch (bootstrap=no)" + + fork_head: + if: >- + github.event_name == 'pull_request_target' && + github.event.pull_request.head.repo.full_name != github.repository + runs-on: ubuntu-latest + steps: + - name: explain deferred fork labels + run: >- + echo "labels: fork head has a read-only token; writes deferred to the scheduled sweep cadence"