Summary
Since #37959 (commit 79a7eb48b, 2026-06-08), on.roles authorization prefers the repository role_name over the base permission. This unintentionally makes custom organization repository roles unauthorizable: an actor whose access is granted through a custom role (e.g. Security Champions) that confers write is rejected, even though their base permission is write. There is no frontmatter configuration that fixes it, because the roles: schema enum rejects custom role-name strings.
This is a fail-closed regression, distinct from the already-tracked #38140 (docs) and #37853 (maintain/triage enum). Neither of those covers custom org roles.
Reproduction
- In an org, create a custom repository role (base = Write), e.g.
Security Champions.
- Grant a user that custom role on a repo using a command/auto agentic workflow (default
roles: [admin, maintainer, write]).
- Have that user trigger the workflow (e.g. a slash command on a PR).
Observed pre_activation log:
Checking if user '<user>' has required permissions for <org>/<repo>
Required permissions: admin, maintainer, write
Repository permission level: write (role: Security Champions)
Warning: User permission 'Security Champions' does not meet requirements: admin, maintainer, write
Expected: authorized — base permission is write, which is in the required set.
Actual: denied.
Root cause
actions/setup/js/check_permissions_utils.cjs, checkRepositoryPermission:
const effectiveRole = normalizedRoleName || normalizedPermission; // role_name wins whenever present
const hasPermission = requiredPermissions.some(requiredPerm => {
const normalizedRequired = requiredPerm === "maintainer" ? "maintain" : requiredPerm;
return normalizedRequired === effectiveRole; // "write" === "Security Champions" → false
});
For GitHub's custom org repository roles, getCollaboratorPermissionLevel returns permission: "write" and role_name: "<custom role name>". Because role_name is always preferred when present, the base permission is never consulted, so any custom role whose name is not literally one of admin/maintainer/maintain/write/triage/read fails.
Introduced in #37959, whose intent was to honor precise maintain/triage roles (base API collapses maintain→write, triage→read). The custom-role case was not considered; the added tests only cover maintain/triage.
Why it is not configurable
The natural workaround — list the custom role name in roles: — does not compile. The schema enum-validates before runtime:
error: value must be one of 'admin', 'maintainer', 'maintain', 'write', 'triage', 'read'
So a custom-role-only actor cannot be authorized by any roles: value. (the runtime check_permissions_utils.cjs uses plain string equality and would match a custom name if it could reach GH_AW_REQUIRED_ROLES — but the compiler blocks it.)
Suggested fix (either)
Add a test fixture with { permission: "write", role_name: "<custom>" } to lock the custom-role case.
Environment
Summary
Since #37959 (commit
79a7eb48b, 2026-06-08),on.rolesauthorization prefers the repositoryrole_nameover the basepermission. This unintentionally makes custom organization repository roles unauthorizable: an actor whose access is granted through a custom role (e.g.Security Champions) that conferswriteis rejected, even though their base permission iswrite. There is no frontmatter configuration that fixes it, because theroles:schema enum rejects custom role-name strings.This is a fail-closed regression, distinct from the already-tracked #38140 (docs) and #37853 (maintain/triage enum). Neither of those covers custom org roles.
Reproduction
Security Champions.roles: [admin, maintainer, write]).Observed
pre_activationlog:Expected: authorized — base permission is
write, which is in the required set.Actual: denied.
Root cause
actions/setup/js/check_permissions_utils.cjs,checkRepositoryPermission:For GitHub's custom org repository roles,
getCollaboratorPermissionLevelreturnspermission: "write"androle_name: "<custom role name>". Becauserole_nameis always preferred when present, the base permission is never consulted, so any custom role whose name is not literally one ofadmin/maintainer/maintain/write/triage/readfails.Introduced in #37959, whose intent was to honor precise
maintain/triageroles (base API collapses maintain→write, triage→read). The custom-role case was not considered; the added tests only covermaintain/triage.Why it is not configurable
The natural workaround — list the custom role name in
roles:— does not compile. The schema enum-validates before runtime:So a custom-role-only actor cannot be authorized by any
roles:value. (the runtimecheck_permissions_utils.cjsuses plain string equality and would match a custom name if it could reachGH_AW_REQUIRED_ROLES— but the compiler blocks it.)Suggested fix (either)
role_nameor the basepermissionsatisfies the required set. This keeps Honorrole_namein runtime role authorization foron.roles(maintain/triage) #37959's precise maintain/triage support while restoring custom-role and general base-permission behavior. EffectivelyhasPermission = matches(effectiveRole) || matches(normalizedPermission).roles:as a minimum threshold, matching the docs' "based on repository permission level" wording (also addresses [deep-report] Document that on.roles is an exact-match allowlist, not a privilege threshold #38140's footgun).Add a test fixture with
{ permission: "write", role_name: "<custom>" }to lock the custom-role case.Environment
github/gh-aw-actions/setup@v0.81.6; also present onmain.role_namein runtime role authorization foron.roles(maintain/triage) #37959 (cause), [deep-report] Document that on.roles is an exact-match allowlist, not a privilege threshold #38140 (docs — states the role_name bug was "fixed"; this custom-role case remains), [deep-report] Resolve schema-valid-but-unreachable maintain/triage values in on.roles enum #37853 (closed not_planned).