From fed6a20ca5cd9ee52796ddeeb62a81821ddd7f8f Mon Sep 17 00:00:00 2001 From: Douglas Ezra Morrison Date: Fri, 31 Jul 2026 19:40:41 -0700 Subject: [PATCH 1/3] ums: pull_request_target is rejected by the App-token exchange Anthropic's exchange endpoint 401s on OIDC tokens minted for pull_request_target, so a review workflow on that trigger never reaches the model. The action's own side supports it, which is what makes the misconfiguration read as correct. Also separates the three 401s that share that step -- the trigger event, a non-collaborator triggering actor, and the workflow-validation skip that exits 0 -- and records a fourth distinct cause in the short-duration band fully-clean.md already warns not to read as a credential signature. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HMmB6db4xpbuqcqaQKArZb --- memories/claude-bot-workflows.md | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/memories/claude-bot-workflows.md b/memories/claude-bot-workflows.md index 41700bd1a..f9c542928 100644 --- a/memories/claude-bot-workflows.md +++ b/memories/claude-bot-workflows.md @@ -939,3 +939,60 @@ mattering. org-level Claude secrets, provisioned in three batches between 2026-05-09 and 2026-07-14. Attribution proved unrecoverable by any of the three surfaces above.) + +## `pull_request_target` is rejected by the App-token exchange + +Anthropic's `github-app-token-exchange` endpoint rejects OIDC tokens minted for +`pull_request_target` events, with +`App token exchange failed: 401 Unauthorized - Invalid OIDC token`. +The job dies in about 25 seconds, before the model is reached. + +The trap is that the action's **own** side supports the event. +claude-code-action#347, "Can't use action in workflow triggered by +`pull_request_target`", is closed as completed, and `docs/security.md` carries a +section on using the action with that trigger --- so the workflow reads as both +correct and documented. +Only the server side refuses it. +(Issue #713's body credits the support to "PR #759". +That number is an unrelated open bug report in the same repo, so cite #347 +instead of repeating it.) +Tracked as +[anthropics/claude-code-action#713](https://github.com/anthropics/claude-code-action/issues/713), +open since 2025-12-02, whose stated workaround is to use `pull_request`. + +So `pull_request_target` is not an available fix for fork PRs, whatever else +recommends it. +Nor would it be sufficient if the exchange worked: the action still refuses to run +for a contributor without write access unless `allowed_non_write_users` is set, +which `docs/security.md` documents as a significant security risk. +Read a proposal to switch a review workflow to this trigger as a regression, and +leave a comment in the workflow naming the upstream issue so it does not get +re-applied. + +Two neighbouring 401s at the same step have different causes, and the message is +what separates them: + +- `Invalid OIDC token` --- the trigger event, this entry. +- `User does not have write access on this repository` --- the triggering actor is + not a collaborator, e.g. the Copilot coding agent + (`UCD-SERG/ucd-serg.github.io#84`). +- `Workflow validation failed ... identical content to the version on the + repository's default branch` --- not a failure at all. + The action **skips** and exits 0, so a green check on a PR that edits the review + workflow is not a review. + See the self-mod skip bullet above for gha's own guard against this. + +This is a fourth distinct cause in the same short-duration band on the same +repository, which is why +[`fully-clean`](../shared/workflow/fully-clean.md)'s "a short duration is +corroboration, not a test" holds: three of the four run 25 seconds or less and +none of them is about credentials. + +(`UCD-SERG/ucd-serg.github.io`, 2026-07-31: PR #83 switched the review workflow to +`pull_request_target`, and all five subsequent runs failed this way while the two +`pull_request` runs immediately before it succeeded. +Reverted in #89, tracked as #88. +The revert PR's own `pull_request` run then stopped at *workflow validation* +instead --- same repo, same secret, same minute as a `pull_request_target` run +rejected at the token --- which is the cleanest available demonstration, since the +trigger is the only variable.) From 4017e659640c163e51d06b9efa9d87814b10eac9 Mon Sep 17 00:00:00 2001 From: Douglas Ezra Morrison Date: Fri, 31 Jul 2026 20:01:35 -0700 Subject: [PATCH 2/3] review: fix a paraphrase quoted as verbatim and a repo overclaim Three findings from round 1, all correct: - The fully-clean.md reference put a paraphrase in quotation marks. Replaced with the section's real heading, verified verbatim, plus the point stated in my own words. - "on the same repository" was wrong: the three antecedent failures are two on ucd-serg.github.io and one on d-morrison/qwt. Named both, and said what they actually share. - "Two neighbouring 401s" introduced three bullets, the third of which is not a 401 at all. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HMmB6db4xpbuqcqaQKArZb --- memories/claude-bot-workflows.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/memories/claude-bot-workflows.md b/memories/claude-bot-workflows.md index f9c542928..8ccab9e6c 100644 --- a/memories/claude-bot-workflows.md +++ b/memories/claude-bot-workflows.md @@ -969,8 +969,9 @@ Read a proposal to switch a review workflow to this trigger as a regression, and leave a comment in the workflow naming the upstream issue so it does not get re-applied. -Two neighbouring 401s at the same step have different causes, and the message is -what separates them: +Three messages arrive at that same exchange step, and only the first two are +401s. +The message is what separates them: - `Invalid OIDC token` --- the trigger event, this entry. - `User does not have write access on this repository` --- the triggering actor is @@ -982,11 +983,17 @@ what separates them: workflow is not a review. See the self-mod skip bullet above for gha's own guard against this. -This is a fourth distinct cause in the same short-duration band on the same -repository, which is why -[`fully-clean`](../shared/workflow/fully-clean.md)'s "a short duration is -corroboration, not a test" holds: three of the four run 25 seconds or less and -none of them is about credentials. +This is a fourth distinct cause in the short-duration band that +[`fully-clean`](../shared/workflow/fully-clean.md) already records three for, +under "That duration signature does not run backwards". +Three of the four run 25 seconds or less, and none of them is about credentials, +which is that section's point: a short run corroborates a credential hypothesis +you already hold on other grounds, and never produces one. + +Three of the four are on `UCD-SERG/ucd-serg.github.io` and one is on +`d-morrison/qwt`, so the band is the thing they share rather than the repo. +Saying otherwise would be the grouping-by-symptom overreach that same section +warns about, in the entry invoking its authority. (`UCD-SERG/ucd-serg.github.io`, 2026-07-31: PR #83 switched the review workflow to `pull_request_target`, and all five subsequent runs failed this way while the two From ac50532f1f5df33bbb4a42359fa3e8da47f4e173 Mon Sep 17 00:00:00 2001 From: Douglas Ezra Morrison Date: Fri, 31 Jul 2026 20:13:17 -0700 Subject: [PATCH 3/3] review: make the skip-exits-0 case explicit in the closing example Round 2 read run 30680266785's success conclusion and reported the sentence describing it as fabricated. The run did succeed and did stop at workflow validation -- the action skips and exits 0, which is what the third bullet of this same section says. Rebutted with the log, and reworded so the conclusion cannot be read as excluding an early exit: both runs are now named, the exit-0 is stated outright, and the misreading itself is recorded as a worked example of the bullet above. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HMmB6db4xpbuqcqaQKArZb --- memories/claude-bot-workflows.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/memories/claude-bot-workflows.md b/memories/claude-bot-workflows.md index 8ccab9e6c..e84cccb83 100644 --- a/memories/claude-bot-workflows.md +++ b/memories/claude-bot-workflows.md @@ -999,7 +999,22 @@ warns about, in the entry invoking its authority. `pull_request_target`, and all five subsequent runs failed this way while the two `pull_request` runs immediately before it succeeded. Reverted in #89, tracked as #88. -The revert PR's own `pull_request` run then stopped at *workflow validation* -instead --- same repo, same secret, same minute as a `pull_request_target` run -rejected at the token --- which is the cleanest available demonstration, since the -trigger is the only variable.) +The revert PR's own two runs are the cleanest demonstration available, because the +trigger is the only variable between them. +Run 30680266779 (`pull_request_target`) was rejected at the token with +`401 Invalid OIDC token`. +Run 30680266785 (`pull_request`) had its token *accepted* and got as far as +workflow validation, where it skipped and exited 0 --- so its check reads +`success` while the action never reviewed anything. +Same repo, same secret, 15 seconds apart. + +That second run is also a worked example of the skip bullet above, and of how it +misleads a careful reader. +Round 2 of this PR's own review read that `success` conclusion and reported the +sentence describing it as a fabricated claim, on the reasoning that a run which +succeeded cannot have stopped early. +It can, and this one did: the log carries +`Skipping action due to workflow validation` and `Exiting due to workflow +validation skip`. +Read the log rather than the conclusion, on any job whose action can exit 0 +without doing its work.)