Summary
For a centralized slash_command workflow (on.slash_command.strategy: centralized), invoking the command on a pull request causes the dispatched pre_activation job to fail its PR-provenance check with a 403, because the job is never granted pull-requests: read. The run finishes with a green "success" conclusion while silently skipping every real step — no error is surfaced to the user beyond a warning-level annotation.
Repro
- Define a centralized slash-command workflow, e.g.:
on:
slash_command:
strategy: centralized
name: changelog
events: [issue_comment, pull_request_comment]
permissions:
contents: read
pull-requests: read
- Compile it (
gh aw compile, tested against v0.81.6 and v0.88.7 — same result on both).
- Comment
/changelog PR #123 on an open pull request in the repo.
- The router (
agentic_commands.yml) dispatches workflow_dispatch to the target workflow, passing aw_context.item_type: "pull_request".
- In the dispatched run's
pre_activation job, check_membership.cjs reaches its workflow_dispatch branch, sees item_type === "pull_request", and calls github.rest.pulls.get({ owner, repo, pull_number }) to verify the PR wasn't forged/isn't from a fork (this is the confused-deputy guard for centralized dispatches — see actions/setup/js/check_membership.cjs lines ~101–174).
- That call 403s:
Resource not accessible by integration.
- The
catch block at line ~166–174 treats any API failure (permission gap included, not just transient errors) as is_team_member: false, so pre_activation.outputs.activated becomes false.
- Every downstream job (
activation, agent, detection, safe_outputs) is skipped via its if: guard. The workflow run still reports conclusion: success because nothing failed — it just did nothing.
Observed in the wild
Repo: GoSmarter-ai/gosmarter-core-platform, workflow changelog-announcement-writer.lock.yml (compiled from changelog-announcement-writer.md, strategy: centralized, events: [issue_comment, pull_request_comment]).
Dispatched run: https://github.com/GoSmarter-ai/gosmarter-core-platform/actions/runs/34594741202
Job annotation:
! Repository permission check failed: Unable to verify pull request provenance (Resource not accessible by integration - https://docs.github.com/rest/pulls/pulls#get-a-pull-request).
pre_activation: .github#23
Confirmed the generated lock file's pre_activation job has no permissions: block at all (top-level workflow permissions: {}), while every other job (activation, agent, detection, safe_outputs, conclusion) does get an explicit, scoped permissions: block. Re-ran gh aw compile after upgrading the extension from v0.81.6 to the latest v0.88.7 — the gap is still present in the newly generated lock file.
Root cause
pkg/workflow/compiler_pre_activation_job.go, buildPreActivationPermissions:
// Auto-grant pull-requests: read when label_command uses decentralized strategy
// with pull_request events. The check_membership.cjs script calls the pulls API
// to verify PR provenance, which requires pull-requests: read.
if data.LabelCommandDecentralized && slices.Contains(FilterLabelCommandEvents(data.LabelCommandEvents), "pull_request") {
if perms == nil {
perms = NewPermissions()
}
perms.Set(PermissionPullRequests, PermissionRead)
}
This auto-grant only fires for decentralized label_command (fixed in #44247/#44282). It does not fire for centralized slash_command (data.CommandCentralized), even though check_membership.cjs's workflow_dispatch branch performs the exact same pulls.get() provenance check for centralized dispatches whenever the dispatched item is a pull request (itemType === "pull_request", set from aw_context.item_type — see lines ~101–174 of check_membership.cjs). That runtime item type can't be known at compile time even when the workflow's declared events list is only issue_comment (an issue_comment webhook fires for PR comments too), so the grant needs to be unconditional for any centralized command, not gated on the declared event list the way the decentralized-label-command case is.
Suggested fix
Extend the same block in buildPreActivationPermissions to also grant pull-requests: read whenever data.CommandCentralized && len(data.Command) > 0. I've drafted this fix + two unit tests locally (unconditional grant test + a "not centralized → no grant" negative test, mirroring the existing TestLabelCommandDecentralizedPreActivationPullRequestsReadPermission pattern in label_command_test.go) but haven't been able to build/test it in my current environment (no Go toolchain available) — happy to open a PR with it if useful, otherwise leaving this as a bug report with the located root cause.
Impact
Every centralized slash-command workflow in an affected repo silently no-ops (with a misleading green conclusion) whenever invoked from a pull request — this is not specific to one workflow, it's structural to the CommandCentralized + pull-request-item code path. In our repo alone this affects /changelog, /code-quality, /review-dependabot, /dependency-review, /lighthouse, /select-tests, /test-coverage, /ui-guidelines, and /security-review — any centralized command that can be invoked on a PR.
Summary
For a centralized
slash_commandworkflow (on.slash_command.strategy: centralized), invoking the command on a pull request causes the dispatchedpre_activationjob to fail its PR-provenance check with a 403, because the job is never grantedpull-requests: read. The run finishes with a green "success" conclusion while silently skipping every real step — no error is surfaced to the user beyond a warning-level annotation.Repro
gh aw compile, tested against v0.81.6 and v0.88.7 — same result on both)./changelog PR #123on an open pull request in the repo.agentic_commands.yml) dispatchesworkflow_dispatchto the target workflow, passingaw_context.item_type: "pull_request".pre_activationjob,check_membership.cjsreaches itsworkflow_dispatchbranch, seesitem_type === "pull_request", and callsgithub.rest.pulls.get({ owner, repo, pull_number })to verify the PR wasn't forged/isn't from a fork (this is the confused-deputy guard for centralized dispatches — seeactions/setup/js/check_membership.cjslines ~101–174).Resource not accessible by integration.catchblock at line ~166–174 treats any API failure (permission gap included, not just transient errors) asis_team_member: false, sopre_activation.outputs.activatedbecomesfalse.activation,agent,detection,safe_outputs) is skipped via itsif:guard. The workflow run still reportsconclusion: successbecause nothing failed — it just did nothing.Observed in the wild
Repo:
GoSmarter-ai/gosmarter-core-platform, workflowchangelog-announcement-writer.lock.yml(compiled fromchangelog-announcement-writer.md,strategy: centralized,events: [issue_comment, pull_request_comment]).Dispatched run:
https://github.com/GoSmarter-ai/gosmarter-core-platform/actions/runs/34594741202Job annotation:
Confirmed the generated lock file's
pre_activationjob has nopermissions:block at all (top-level workflowpermissions: {}), while every other job (activation,agent,detection,safe_outputs,conclusion) does get an explicit, scopedpermissions:block. Re-rangh aw compileafter upgrading the extension from v0.81.6 to the latest v0.88.7 — the gap is still present in the newly generated lock file.Root cause
pkg/workflow/compiler_pre_activation_job.go,buildPreActivationPermissions:This auto-grant only fires for decentralized
label_command(fixed in #44247/#44282). It does not fire for centralizedslash_command(data.CommandCentralized), even thoughcheck_membership.cjs'sworkflow_dispatchbranch performs the exact samepulls.get()provenance check for centralized dispatches whenever the dispatched item is a pull request (itemType === "pull_request", set fromaw_context.item_type— see lines ~101–174 ofcheck_membership.cjs). That runtime item type can't be known at compile time even when the workflow's declaredeventslist is onlyissue_comment(anissue_commentwebhook fires for PR comments too), so the grant needs to be unconditional for any centralized command, not gated on the declared event list the way the decentralized-label-command case is.Suggested fix
Extend the same block in
buildPreActivationPermissionsto also grantpull-requests: readwheneverdata.CommandCentralized && len(data.Command) > 0. I've drafted this fix + two unit tests locally (unconditional grant test + a "not centralized → no grant" negative test, mirroring the existingTestLabelCommandDecentralizedPreActivationPullRequestsReadPermissionpattern inlabel_command_test.go) but haven't been able to build/test it in my current environment (no Go toolchain available) — happy to open a PR with it if useful, otherwise leaving this as a bug report with the located root cause.Impact
Every centralized slash-command workflow in an affected repo silently no-ops (with a misleading green conclusion) whenever invoked from a pull request — this is not specific to one workflow, it's structural to the
CommandCentralized+ pull-request-item code path. In our repo alone this affects/changelog,/code-quality,/review-dependabot,/dependency-review,/lighthouse,/select-tests,/test-coverage,/ui-guidelines, and/security-review— any centralized command that can be invoked on a PR.