Skip to content

Centralized slash_command pre_activation missing pull-requests:read, causes silent no-op with success conclusion on PR-invoked commands #60268

Description

@stephlocke

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

  1. 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
  2. Compile it (gh aw compile, tested against v0.81.6 and v0.88.7 — same result on both).
  3. Comment /changelog PR #123 on an open pull request in the repo.
  4. The router (agentic_commands.yml) dispatches workflow_dispatch to the target workflow, passing aw_context.item_type: "pull_request".
  5. 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).
  6. That call 403s: Resource not accessible by integration.
  7. 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.
  8. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions