From 5bbdf495584efdb5e824ea950ac0e02f25c2e423 Mon Sep 17 00:00:00 2001 From: Elior Erez Date: Thu, 6 Aug 2026 19:01:48 -0400 Subject: [PATCH 1/2] Add one-time manual diagnostic to list org fine-grained permissions github-config#173 shipped a github_organization_role permission string (manage_organization_runners) that GitHub's API rejected with 422 Invalid permission -- GitHub's docs only show the UI label for these, not the identifier string. Rather than guess again, add a read-only, manual-only workflow_dispatch step that queries the live organization-fine-grained-permissions API via the same app token Terraform already uses, so the real value can be read once from the job summary instead of guessed. NO-ISSUE --- .github/workflows/apply.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/apply.yaml b/.github/workflows/apply.yaml index e1b5be9..22d54c5 100644 --- a/.github/workflows/apply.yaml +++ b/.github/workflows/apply.yaml @@ -19,6 +19,10 @@ on: description: "One-time: comma-separated Terraform resource addresses to exclude from this apply (e.g. a resource that's known-broken and blocking every other pending change atomically, while a permanent fix is prepared). Leave empty for a normal apply." required: false default: "" + list_org_fine_grained_permissions: + description: "One-time: set to 'true' to print the org's live custom-role fine-grained permission names to the job summary (e.g. to find the exact permission string for a github_organization_role, since GitHub's docs only show UI labels, not identifiers). Read-only, runs before TF Apply. Leave empty for a normal apply." + required: false + default: "" schedule: - cron: "17 */4 * * *" push: @@ -65,6 +69,27 @@ jobs: client-id: Iv23lipEOAvwk5QqNUie private-key: ${{ secrets.CONFIG_APP_SECRET }} owner: ${{ github.repository_owner }} + # Read-only lookup, no Terraform involved -- GitHub's docs only show + # the UI label for custom-org-role permissions (e.g. "Manage + # organization runners and runner groups"), not the identifier string + # a github_organization_role resource actually needs. This queries + # the live API via the same app token Terraform itself uses, instead + # of guessing. Manual, one-time use via workflow_dispatch input; a + # no-op (skipped entirely) for the normal scheduled/push triggers, + # which never set this input. + - name: List org fine-grained permissions (one-time, manual only) + if: inputs.list_org_fine_grained_permissions == 'true' + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + run: | + gh api "orgs/${{ github.repository_owner }}/organization-fine-grained-permissions" > /tmp/org-perms.json + { + echo "### All org fine-grained permissions" + jq -r '.[] | "- `\(.name)`: \(.description)"' /tmp/org-perms.json + echo + echo "### Runner-related" + jq -r '.[] | select(.description | test("runner"; "i")) | "- `\(.name)`: \(.description)"' /tmp/org-perms.json + } >> "$GITHUB_STEP_SUMMARY" - name: Setup OpenTofu uses: opentofu/setup-opentofu@v2 with: From 5e7700d1022e2ef3ac94cdb5ca179fb44e40fbf1 Mon Sep 17 00:00:00 2001 From: Elior Erez Date: Fri, 7 Aug 2026 01:01:55 -0400 Subject: [PATCH 2/2] Make the org-permissions diagnostic actually read-only TF Apply had no guard at all, so a dispatch setting only list_org_fine_grained_permissions=true would still run a full mutating apply afterward, contradicting the step's own read-only description. Gate TF Import, TF State Remove, and TF Apply on this input being unset, and clear the unused AWS backend credentials from the diagnostic step's own env, matching the existing pattern on the 'File an issue on apply failure' step. --- .github/workflows/apply.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/apply.yaml b/.github/workflows/apply.yaml index 22d54c5..22ec489 100644 --- a/.github/workflows/apply.yaml +++ b/.github/workflows/apply.yaml @@ -20,7 +20,7 @@ on: required: false default: "" list_org_fine_grained_permissions: - description: "One-time: set to 'true' to print the org's live custom-role fine-grained permission names to the job summary (e.g. to find the exact permission string for a github_organization_role, since GitHub's docs only show UI labels, not identifiers). Read-only, runs before TF Apply. Leave empty for a normal apply." + description: "One-time: set to 'true' to print the org's live custom-role fine-grained permission names to the job summary (e.g. to find the exact permission string for a github_organization_role, since GitHub's docs only show UI labels, not identifiers). Read-only: skips TF Import/TF State Remove/TF Apply for this run. Leave empty for a normal apply." required: false default: "" schedule: @@ -80,6 +80,11 @@ jobs: - name: List org fine-grained permissions (one-time, manual only) if: inputs.list_org_fine_grained_permissions == 'true' env: + # Only gh + GITHUB_TOKEN are needed here -- explicitly clear the + # job-level AWS backend credentials rather than let this step + # inherit them unnecessarily. + AWS_ACCESS_KEY_ID: "" + AWS_SECRET_ACCESS_KEY: "" GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} run: | gh api "orgs/${{ github.repository_owner }}/organization-fine-grained-permissions" > /tmp/org-perms.json @@ -108,7 +113,7 @@ jobs: # inputs; a no-op (skipped entirely) for the normal scheduled/push # triggers, which never set these inputs. - name: TF Import (one-time, manual only) - if: inputs.import_address != '' + if: inputs.import_address != '' && inputs.list_org_fine_grained_permissions != 'true' run: | tofu import "${{ inputs.import_address }}" "${{ inputs.import_id }}" env: @@ -122,7 +127,7 @@ jobs: # no-op (skipped entirely) for the normal scheduled/push triggers, # which never set this input. - name: TF State Remove (one-time, manual only) - if: inputs.state_rm_addresses != '' + if: inputs.state_rm_addresses != '' && inputs.list_org_fine_grained_permissions != 'true' run: | if [[ "${STATE_RM_ADDRESSES}" == *$'\n'* ]]; then echo "::error::state_rm_addresses must be comma-separated on a single line, not newline-separated." >&2 @@ -167,6 +172,7 @@ jobs: # *not* downstream of the broken one, not literally everything else. - name: TF Apply id: tofu_apply + if: inputs.list_org_fine_grained_permissions != 'true' run: | EXCLUDE_ARGS=() if [[ -n "${EXCLUDE_ADDRESSES}" ]]; then