Skip to content

feat(ci): mint an App token for the presentation-branch refresh - #44

Merged
github-actions[bot] merged 3 commits into
developfrom
feat/refresh-master-app-token
Aug 17, 2026
Merged

feat(ci): mint an App token for the presentation-branch refresh#44
github-actions[bot] merged 3 commits into
developfrom
feat/refresh-master-app-token

Conversation

@nolte

@nolte nolte commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

Refreshing main under GITHUB_TOKEN cannot work at all. GitHub rejects a push
from a GitHub App identity that creates or updates any file under
.github/workflows/ unless the installation holds the workflows permission —
and GITHUB_TOKEN is such an identity. Forward the portfolio App credentials so
the reusable mints a short-lived installation token for the push instead.

Changes

  • Forward app-id: ${{ vars.PORTFOLIO_APP_ID }} and
    secrets.app-private-key: ${{ secrets.PORTFOLIO_APP_PRIVATE_KEY }} to
    reusable-release-cd-refresh-master.yml.
  • Replace the deferral comment at the call site with the established reason and
    the permission set the installation must hold.

Linked issues

None

Testing

  • task --yes lint — all pre-commit hooks pass.
  • python3 -c "yaml.safe_load(...)" — parses; the job now carries
    secrets: [token, app-private-key] and with: [target_branch, from_branch, app-id], matching the reusable's declared signature at d51e51e.
  • Established, from run
    https://github.com/nolte/taskfiles/actions/runs/32065009956 dispatched right
    after fix(ci): make the refresh-master manual dispatch usable #43: FROM_REF: v0.1.5 resolved correctly and the push was attempted,
    then rejected with refusing to allow a GitHub App to create or update workflow .github/workflows/release-cd-refresh-master.yml without 'workflows' permission.
  • Established, git diff --name-only origin/main 57450b5 -- .github/workflows/:
    seven workflow files differ between main and the release tag, so the reset
    necessarily writes under .github/workflows/.
  • Established, gh api repos/nolte/taskfiles/branches/main/protection404 Branch not protected: the rejection is not a branch-protection effect.
  • Established, GitHub Docs workflow-syntax §permissions: the settable scope
    list carries no workflows entry, so no permissions: change can grant it.
  • Not established — this is the merge gate. Whether the installation behind
    PORTFOLIO_APP_ID (3784868) holds contents: write and workflows: write
    on this repository. gh api /user/installations returns 403 without App
    authentication, so it can only be read from the App's settings page.

Risk / rollout notes

Medium, and gated. The change swaps the push identity on both the automatic
release: published path and the manual dispatch path.

  • If the installation holds both permissions, the refresh works for the first
    time and main catches up.
  • If the mint fails outright (App not installed), the reusable's
    continue-on-error falls back to secrets.token and behaviour is exactly
    today's — still broken, no worse.
  • If the mint succeeds but a permission is missing, there is no fallback and
    the push fails. This is why the PR stays draft until the installation
    permissions are confirmed.

Second effect, deliberate: App-token pushes do cascade downstream, which is the
point of the remediation. build-static-tests.yaml triggers on an unfiltered
on: push:, so each successful refresh of main will additionally start
pre-commit, trivy and chain-bench there, and automerge.yaml triggers on
check_suite. If that noise is unwanted, the fix is a branch filter in
build-static-tests.yaml, not reverting this.

The platform constraint behind this PR is recorded portfolio-wide in
nolte/claude-shared#550, spec/project/workflow-health/ §Known platform
constraints.

Originating source: workflow-health triage of the red release-cd-refresh-master.yml run https://github.com/nolte/taskfiles/actions/runs/32065009956
Dispatched specialist: no matching specialist existed — generalist handled

Refreshing `main` under GITHUB_TOKEN cannot work. GitHub rejects a push from a
GitHub App identity that creates or updates any file under .github/workflows/
unless the installation holds the `workflows` permission; GITHUB_TOKEN is such
an identity, and the workflow permissions: block exposes no `workflows` scope
to grant it. The refresh resets `main` to the release tag, and that diff carries
workflow files whenever `main` has fallen behind — seven of them at the time of
writing.

Observed in run 32065009956, dispatched right after #43 fixed the empty
`from_branch`:

  ! [remote rejected] 57450b5 -> main (refusing to allow a GitHub App to create
    or update workflow `.github/workflows/release-cd-refresh-master.yml`
    without `workflows` permission)

`main` carries no branch protection at all, so this is not a protection
problem and no permissions: change can lift it. Forward vars.PORTFOLIO_APP_ID
and secrets.PORTFOLIO_APP_PRIVATE_KEY so the reusable mints a short-lived
installation token for the push.

This reverses the reasoning removed in b16fc1d, which called the App wiring an
audit-trail improvement deferred until gh-plumbing#330 Phase 2. That was wrong
for this repository: the wiring is the only path that works today.

PREREQUISITE, NOT YET VERIFIED: the installation behind PORTFOLIO_APP_ID must
hold both `contents: write` and `workflows: write` on this repository. The
reusable's mint step is continue-on-error, which only covers a failed mint — a
successful mint with insufficient permissions has no fallback to secrets.token
and fails the push. Confirm the installation permissions before merging; this
PR stays draft until then.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A4KA8swD1ZzDqknDV5TUxx
nolte and others added 2 commits August 17, 2026 23:34
Review found the App wiring degrades silently. The reusable's mint step is
`continue-on-error`, so if `PORTFOLIO_APP_ID` is set but
`PORTFOLIO_APP_PRIVATE_KEY` is missing or stale — a key rotated in the App
settings without updating the repo secret — the mint fails, the error is
swallowed, `outputs.token` is empty, and the push falls back to GITHUB_TOKEN.
It then dies with exactly the `refusing to allow a GitHub App to create or
update workflow ...` rejection this PR wires the App token in to avoid, while
the real cause appears only as an annotation on a step that didn't fail.

Check both values in the existing validate_ref job, which already runs on both
trigger paths. Both set or both empty pass; any mismatch fails with a message
naming which half is missing.

Verified by extracting the step's script and running the four combinations:
true/true and false/false pass, true/false and false/true fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A4KA8swD1ZzDqknDV5TUxx
@nolte
nolte marked this pull request as ready for review August 17, 2026 21:44
@nolte nolte added the automerge Allow automatic Merge. label Aug 17, 2026
@github-actions
github-actions Bot merged commit 946069a into develop Aug 17, 2026
8 checks passed
@github-actions
github-actions Bot deleted the feat/refresh-master-app-token branch August 17, 2026 21:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge Allow automatic Merge. chore Maintenance cicd ci/cd process functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant