From 1ff660f539c02e683c9d2c007932dc9fa614d9a7 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl <304681515+codex-bot-andresmgsl@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:21:11 +0000 Subject: [PATCH] docs: preserve labels workflow safety rationale --- .github/workflows/labels.yml | 18 ++++++++++++++++++ actions/labels-reconcile/labels-reconcile.sh | 8 +++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index ae976b1..e682fdf 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -1,6 +1,23 @@ 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 — labeler reads changed paths via the +# API, and reconcile checks out the BASE branch only. Keep it that way. +# +# There is no pull_request_review_target, so a review landing cannot wake this +# workflow directly — and the */15 cron is advisory: GitHub deprioritises +# short intervals hard enough that a quiet repo goes hours between ticks. The +# handoff wakes the sweep itself: the author sets state:needs-human, and the +# caller's `labeled` event confirms or corrects that optimistic write within +# seconds. The cron stays as the last resort for a forgotten handoff. +# +# This cannot loop: reconciler writes use GITHUB_TOKEN, and GitHub does not +# create workflow runs from GITHUB_TOKEN-triggered events. Agent writes use a +# PAT and therefore do trigger — exactly the asymmetry wanted. on: workflow_call: @@ -24,6 +41,7 @@ jobs: steps: - uses: actions/labeler@v5 with: + # labeler reads the consumer's .github/labeler.yml via the API # additive only — a hand-applied scope must survive the machine sync-labels: false diff --git a/actions/labels-reconcile/labels-reconcile.sh b/actions/labels-reconcile/labels-reconcile.sh index e045649..8e72b35 100755 --- a/actions/labels-reconcile/labels-reconcile.sh +++ b/actions/labels-reconcile/labels-reconcile.sh @@ -22,7 +22,8 @@ fi # by requesting the human's review — an explicit request is a fact, and it is # the one this machine trusts (see decide_state's top precedence). The # machine auto-requests the human only in the no-judgment-needed case: every -# required verdict is a formal head-current approval. Any approval that counts must be bound to +# required verdict is a formal head-current approval. Any approval that counts +# must be bound to # the CURRENT head SHA: GitHub keeps approvals alive across pushes, and a # stale approval must never promote unreviewed code to the human. # @@ -32,7 +33,7 @@ fi # sweep tolerates a missing label rather than recreating it. # # The state machine below is pure (globals in, state out) and covered by -# fixture tests in test/labels-reconcile.sh. +# fixture tests in test/labels-reconcile.test.sh. HUMAN="${HUMAN_REVIEWER:-danmt}" BOTS=() @@ -394,7 +395,8 @@ reconcile_pr() { # $1 = PR number; relies on the globals set from its fetch desired="$(decide_state)" # encode the runbook's last step for the no-judgment case: every required - # verdict is a head-current approval → the human is asked, once. The guard asks whether + # verdict is a head-current approval → the human is asked, once. The guard + # asks whether # a FRESH human review is needed for THIS head — never "has the human ever # reviewed", which wedged the handoff after any earlier human comment. # Idempotent (a live request suppresses it); race-free via the shared