From 001eca045e5b26efb620b018941928725b7e7379 Mon Sep 17 00:00:00 2001 From: David White Date: Mon, 24 Aug 2026 10:58:19 +0100 Subject: [PATCH 1/8] feat(ci): publication becomes a dispatch; the fluxhashes signer derives the hash itself Replaces the clone-edit-push publish body: this workflow no longer holds write access to fluxhashes at all. It asks the signer there to publish this commit; the signer fetches the commit from this public repository, computes the tree hash from the bytes it fetched, and emits the list, the signed document and provenance in one atomic commit. A compromised token here can trigger signer runs and nothing else -- it cannot put a hash value into the list. The early Check Hash computation survives as claimed_hash, a tripwire the signer compares against its own derivation: a mismatch is a red signing run, catching runner-image drift that would otherwise silently publish a hash no node ever matches. The fork guard replaces a failing secret lookup with an explicit skip. API_TOKEN_GITHUB stays for the fluxjsdocs docs push; the new FLUXHASHES_DISPATCH_TOKEN carries Actions permission on fluxhashes and nothing else. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01SFrS7Q3JwuPj6Yr4vALnwp --- .github/workflows/nodejs.yml | 63 +++++++++++++++++------------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 46a4b6cf77..09adebd273 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -3,6 +3,36 @@ name: Node CI on: [push] jobs: + # Publication is a request, not a write: the fluxhashes signer fetches this commit itself, + # derives the tree hash from the bytes it fetched, and publishes list, signed document and + # provenance in one commit. The token can trigger that workflow and nothing else. NEW_HASH rides + # along as a tripwire the signer checks against its own computation -- a mismatch is a red + # signing run, never a listed value. Forks publish nothing. + # + # Its own job, deliberately: the hash describes a checkout nothing else has touched, and a + # publication failure (fluxhashes unavailable, token expired) stays a red publish job instead of + # blocking the test suite. + publish: + runs-on: ubuntu-22.04 + if: github.repository == 'RunOnFlux/flux' + steps: + - uses: actions/checkout@v7 + - name: Check Hash + run: | + newhash=$(find ./ZelBack -type f -exec md5sum {} + | awk '{print $1}' | LC_ALL=C sort | md5sum | awk '{printf $1}') + echo $newhash + echo NEW_HASH=$newhash >> $GITHUB_ENV + - name: Request hash publication + env: + GH_TOKEN: ${{ secrets.FLUXHASHES_DISPATCH_TOKEN }} + run: | + gh api -X POST repos/RunOnFlux/fluxhashes/actions/workflows/sign-hashlist.yml/dispatches \ + -f ref=master \ + -f "inputs[commit]=${GITHUB_SHA}" \ + -f "inputs[ref]=${GITHUB_REF_NAME}" \ + -f "inputs[ref_type]=${GITHUB_REF_TYPE}" \ + -f "inputs[claimed_hash]=${NEW_HASH}" + build: runs-on: ${{ matrix.os }} @@ -24,39 +54,6 @@ jobs: uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} - - name: Check Hash - run: | - newhash=$(find ./ZelBack -type f -exec md5sum {} + | awk '{print $1}' | LC_ALL=C sort | md5sum | awk '{printf $1}') - echo $newhash - echo NEW_HASH=$newhash >> $GITHUB_ENV - - name: Get current hashes - run: | - mkdir hashes - wget 'https://raw.githubusercontent.com/RunOnFlux/fluxhashes/master/src/hashes/hashes.js' -P hashes - hashfile=`cat hashes/hashes.js | sed "s/return/_/gi" | sed "s/\n/_/gi"` - echo HASH_FILE=$hashfile >> $GITHUB_ENV - - name: Show hashes - run: | - echo $HASH_FILE - echo $NEW_HASH - - name: Patch hashes - if: ${{ !contains(env.HASH_FILE, env.NEW_HASH) }} - run: | - newhash=$(find ./ZelBack -type f -exec md5sum {} + | awk '{print $1}' | LC_ALL=C sort | md5sum | awk '{printf $1}') - sed -i "s/ ];/ '$newhash',\n ];/gi" hashes/hashes.js - tail -n 200 hashes/hashes.js - - name: Push hashes to fluxhashes - if: ${{ !contains(env.HASH_FILE, env.NEW_HASH) }} - uses: cpina/github-action-push-to-another-repository@main - env: - API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} - with: - source-directory: "hashes" - destination-github-username: "RunOnFlux" - destination-repository-name: "fluxhashes" - user-email: runonfluxbot@gmail.com - target-branch: master - target-directory: src/hashes/ - name: install flux and flux benchmark daemons run: | echo 'deb https://apt.runonflux.io/ '$(lsb_release -cs)' main' | sudo tee /etc/apt/sources.list.d/flux.list From 664df60e714b93200c9a7f1cde4c004eb4c5b9a1 Mon Sep 17 00:00:00 2001 From: David White Date: Mon, 24 Aug 2026 12:47:24 +0100 Subject: [PATCH 2/8] feat(ci): the dispatch and docs credentials become GitHub Apps; the classic PAT dies entirely Both cross-repo credentials are now GitHub Apps that mint a short-lived installation token per run -- nothing long-lived to leak and nothing to renew. flux-hashlist-dispatch holds Actions on fluxhashes only (verified: contents writes refused); flux-docs-push holds Contents on fluxjsdocs only. This removes the workflow's last reference to API_TOKEN_GITHUB -- the classic PAT with org-wide repo scope -- so migration revokes it with nothing to replace. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01SFrS7Q3JwuPj6Yr4vALnwp --- .github/workflows/nodejs.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 09adebd273..1758577fdb 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -22,9 +22,20 @@ jobs: newhash=$(find ./ZelBack -type f -exec md5sum {} + | awk '{print $1}' | LC_ALL=C sort | md5sum | awk '{printf $1}') echo $newhash echo NEW_HASH=$newhash >> $GITHUB_ENV + # The credential is a GitHub App scoped to fluxhashes with Actions permission only: the + # workflow mints a short-lived installation token per run, so there is no long-lived token + # anywhere and nothing to renew. + - name: Mint the dispatch token + id: dispatch-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.FLUXHASHES_APP_ID }} + private-key: ${{ secrets.FLUXHASHES_APP_KEY }} + owner: RunOnFlux + repositories: fluxhashes - name: Request hash publication env: - GH_TOKEN: ${{ secrets.FLUXHASHES_DISPATCH_TOKEN }} + GH_TOKEN: ${{ steps.dispatch-token.outputs.token }} run: | gh api -X POST repos/RunOnFlux/fluxhashes/actions/workflows/sign-hashlist.yml/dispatches \ -f ref=master \ @@ -90,10 +101,20 @@ jobs: CI: true - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 + - name: Mint the docs token + if: github.repository == 'RunOnFlux/flux' + id: docs-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.FLUXJSDOCS_APP_ID }} + private-key: ${{ secrets.FLUXJSDOCS_APP_KEY }} + owner: RunOnFlux + repositories: fluxjsdocs - name: Push docs to other repo # Push services directory from RunOnFlux/flux to RunOnFlux/fluxjsdocs repo to build JSDocs separately. + if: github.repository == 'RunOnFlux/flux' uses: cpina/github-action-push-to-another-repository@main env: - API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} + API_TOKEN_GITHUB: ${{ steps.docs-token.outputs.token }} with: source-directory: "ZelBack/src/services" destination-github-username: "RunOnFlux" From 2d70812e3d873d8098a56af67f83ad679562cf03 Mon Sep 17 00:00:00 2001 From: David White Date: Mon, 24 Aug 2026 12:50:59 +0100 Subject: [PATCH 3/8] fix(ci): review round one -- least-privilege run token, publish timeout, honest containment comment The workflow's own GITHUB_TOKEN drops to contents: read (everything cross-repo uses the per-run app tokens); the publish job gets a 5-minute timeout instead of occupying a runner for the 6-hour default on a hung call; and the credential comment now states what Actions permission actually grants -- start, cancel, re-run, delete logs; availability, never content -- instead of overstating the containment. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01SFrS7Q3JwuPj6Yr4vALnwp --- .github/workflows/nodejs.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 1758577fdb..4907fd72be 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -2,12 +2,19 @@ name: Node CI on: [push] +# Cross-repo pushes and dispatches use per-run GitHub App tokens minted below; the run's own token +# needs read only. +permissions: + contents: read + jobs: # Publication is a request, not a write: the fluxhashes signer fetches this commit itself, # derives the tree hash from the bytes it fetched, and publishes list, signed document and - # provenance in one commit. The token can trigger that workflow and nothing else. NEW_HASH rides - # along as a tripwire the signer checks against its own computation -- a mismatch is a red - # signing run, never a listed value. Forks publish nothing. + # provenance in one commit. The credential can start, cancel and re-run workflows on fluxhashes + # and delete their logs -- availability, never content: it cannot write to the repository, and a + # hash value cannot enter the list through it. NEW_HASH rides along as a tripwire the signer + # checks against its own computation -- a mismatch is a red signing run, never a listed value. + # Forks publish nothing. # # Its own job, deliberately: the hash describes a checkout nothing else has touched, and a # publication failure (fluxhashes unavailable, token expired) stays a red publish job instead of @@ -15,6 +22,7 @@ jobs: publish: runs-on: ubuntu-22.04 if: github.repository == 'RunOnFlux/flux' + timeout-minutes: 5 steps: - uses: actions/checkout@v7 - name: Check Hash From 13ef13e11408e201c8c08de78ebab29272ec9659 Mon Sep 17 00:00:00 2001 From: David White Date: Tue, 25 Aug 2026 11:01:42 +0100 Subject: [PATCH 4/8] fix(ci): only development publishes the docs The docs push carried no ref condition, so every branch build raced for fluxjsdocs master and the published JSDocs were whichever branch happened to build last -- three separate feature branches overwrote it within ten seconds on 2026-08-25, and it was serving an unmerged branch's services tree at the time this was found. Gate both docs steps on development. The token mint is gated too: there is no reason to mint a credential for a push that will not happen. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HQKpZbuxYqrWeErU7vKgoA --- .github/workflows/nodejs.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 4907fd72be..d42c91fede 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -109,8 +109,11 @@ jobs: CI: true - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 + # Only development publishes the docs. Without the ref condition every branch build races for + # fluxjsdocs master, so the published JSDocs are whichever branch happened to build last -- + # an unmerged feature branch as often as not. - name: Mint the docs token - if: github.repository == 'RunOnFlux/flux' + if: github.repository == 'RunOnFlux/flux' && github.ref == 'refs/heads/development' id: docs-token uses: actions/create-github-app-token@v1 with: @@ -119,7 +122,7 @@ jobs: owner: RunOnFlux repositories: fluxjsdocs - name: Push docs to other repo # Push services directory from RunOnFlux/flux to RunOnFlux/fluxjsdocs repo to build JSDocs separately. - if: github.repository == 'RunOnFlux/flux' + if: github.repository == 'RunOnFlux/flux' && github.ref == 'refs/heads/development' uses: cpina/github-action-push-to-another-repository@main env: API_TOKEN_GITHUB: ${{ steps.docs-token.outputs.token }} From 30c66fa00c26da0e0ea4497c40af55f093edbb0c Mon Sep 17 00:00:00 2001 From: David White Date: Tue, 25 Aug 2026 12:21:46 +0100 Subject: [PATCH 5/8] docs(ci): state what the dispatch credential is actually trusted to name The publish job's comment claimed "a hash value cannot enter the list through it". That is false. A fork network shares one object store, so a dispatched commit may be any commit ever pushed to this repository or to a public fork of it, and the signer derives whatever it is pointed at. What holds is narrower: the credential sends pointers rather than hash values and cannot write to fluxhashes. That bounds the credential; it does not vouch for the tree. The credential is reachable by anyone who can land a workflow change on any branch here, who can already get a hash listed by pushing one -- the same trust boundary, not a defence against it. The claim mattered because it is the reasoning used to accept looser third-party action pins elsewhere in this file. Wording only; no behaviour change. Reported by Cabecinha84 on #1789. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HQKpZbuxYqrWeErU7vKgoA --- .github/workflows/nodejs.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index d42c91fede..8392c19c09 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -11,10 +11,14 @@ jobs: # Publication is a request, not a write: the fluxhashes signer fetches this commit itself, # derives the tree hash from the bytes it fetched, and publishes list, signed document and # provenance in one commit. The credential can start, cancel and re-run workflows on fluxhashes - # and delete their logs -- availability, never content: it cannot write to the repository, and a - # hash value cannot enter the list through it. NEW_HASH rides along as a tripwire the signer - # checks against its own computation -- a mismatch is a red signing run, never a listed value. - # Forks publish nothing. + # and delete their logs; it cannot write to the repository, and it sends pointers rather than hash + # values. That bounds what the credential does, but it does not make the list unreachable through + # it: a fork network shares one object store, so a dispatched commit may be any commit ever pushed + # to this repository or to a public fork of it, and the signer derives what it is pointed at. The + # credential is held by whoever can land a workflow change on any branch here, who can already get + # a hash listed by pushing -- the same trust boundary, not a defence against it. + # NEW_HASH rides along as a tripwire the signer checks against its own computation -- a mismatch + # is a red signing run, never a listed value. Forks publish nothing. # # Its own job, deliberately: the hash describes a checkout nothing else has touched, and a # publication failure (fluxhashes unavailable, token expired) stays a red publish job instead of From fd184c6af7cda933a281d1e7237e87e651d5987e Mon Sep 17 00:00:00 2001 From: David White Date: Tue, 25 Aug 2026 12:24:38 +0100 Subject: [PATCH 6/8] fix(ci): fail here when nothing was hashed, rather than stalling the signer Check Hash took the exit status of the last element of its pipeline, so a missing ZelBack made find error to stderr while the step exited 0 with newhash set to d41d8cd98f00b204e9800998ecf8427e -- the md5 of an empty stream, and a well-formed 32-hex value that nothing downstream distinguished from a real tree hash. The signer now refuses to list that value, so it cannot reach the published list. But a claim the signer cannot match aborts the signing run, which puts the red in a different repository from the mistake and stalls publication until the next clean push or the daily sweep. Failing here keeps the diagnosis where the cause is. Two ways in, and the second is not a pipeline failure: ZelBack absent, which pipefail catches, and ZelBack present holding no regular files, where find exits 0 and emits nothing. Hence both set -eo pipefail and the explicit comparison -- a bare [ -d ./ZelBack ] would miss the second. Verified by extracting this step's script verbatim from the workflow and running it against three fixtures: absent ZelBack exits 1 on find's own error, empty ZelBack exits 1 on the guard, and a real tree returns the same hash it did before. Reported by Cabecinha84 on #1789. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HQKpZbuxYqrWeErU7vKgoA --- .github/workflows/nodejs.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 8392c19c09..708b384a1e 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -31,7 +31,18 @@ jobs: - uses: actions/checkout@v7 - name: Check Hash run: | + set -eo pipefail newhash=$(find ./ZelBack -type f -exec md5sum {} + | awk '{print $1}' | LC_ALL=C sort | md5sum | awk '{printf $1}') + # d41d8... is the md5 of an empty stream, which the pipeline yields whenever nothing was + # hashed: ZelBack absent (pipefail catches that) or present holding no regular files + # (find exits 0 and emits nothing, which pipefail cannot see). It is a well-formed hash + # meaning "a node whose ZelBack holds no files is genuine FluxOS". The signer refuses to + # list it, but a claim it cannot match aborts the signing run rather than this one -- + # so fail here, in the repository that produced it. + if [ "$newhash" = d41d8cd98f00b204e9800998ecf8427e ]; then + echo 'nothing was hashed -- this checkout has no ZelBack files' + exit 1 + fi echo $newhash echo NEW_HASH=$newhash >> $GITHUB_ENV # The credential is a GitHub App scoped to fluxhashes with Actions permission only: the From 4e9fda8c981450160f19f1b7793e17eae212831d Mon Sep 17 00:00:00 2001 From: David White Date: Tue, 25 Aug 2026 13:07:25 +0100 Subject: [PATCH 7/8] fix(ci): push the docs with git instead of a third-party action cpina/github-action-push-to-another-repository was referenced by tag @main -- a personal repository's default branch -- so every build downloaded and ran whatever was on it at that moment, and handed it a token that can write to fluxjsdocs. Pinning to a commit would close that, but the action is doing nine lines of work: clone the destination shallow, wipe the target directory, copy the source over it, commit if anything changed, push. The other 166 lines are options we do not pass, error strings, and git-lfs setup we do not need. Doing it inline removes the dependency rather than pinning it, which is the same argument this PR makes about the credential itself. Two things improve on the action besides: fluxjsdocs is public, so the clone is anonymous and the token authenticates only the push, where the action put it in the clone URL and therefore the clone's stored config; and `git add -A services` scopes the commit to the published directory instead of `git add .` over the whole destination checkout. Behaviour is preserved deliberately, including the commit message format "Update from " so the fluxjsdocs history stays uniform. Verified by extracting this step verbatim from the workflow, redirecting only the two GitHub URLs at local repositories, and running it: a changed file updates, a file deleted upstream disappears downstream, nested directories are copied, an unrelated directory in the destination is left alone, the author and commit message match what the action produced, and a second run with identical content prints "docs unchanged" and pushes nothing -- master unmoved, no empty commit. Raised by Cabecinha84 on #1789 as a follow-up; taken now since it is the same PR that argues for containment. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HQKpZbuxYqrWeErU7vKgoA --- .github/workflows/nodejs.yml | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 708b384a1e..9233c47f00 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -136,15 +136,31 @@ jobs: private-key: ${{ secrets.FLUXJSDOCS_APP_KEY }} owner: RunOnFlux repositories: fluxjsdocs - - name: Push docs to other repo # Push services directory from RunOnFlux/flux to RunOnFlux/fluxjsdocs repo to build JSDocs separately. + # Git directly, rather than a third-party action. The action this replaces was referenced by + # a personal repository's default branch, so every build ran whatever happened to be on it at + # that moment -- and was handed a token that can write to fluxjsdocs. Nothing outside Flux + # touches the credential now. fluxjsdocs is public, so the clone is anonymous and the token + # authenticates only the push, which also keeps it out of the clone's stored config. + - name: Push docs to fluxjsdocs # Publishes ZelBack/src/services so JSDocs builds separately. if: github.repository == 'RunOnFlux/flux' && github.ref == 'refs/heads/development' - uses: cpina/github-action-push-to-another-repository@main env: - API_TOKEN_GITHUB: ${{ steps.docs-token.outputs.token }} - with: - source-directory: "ZelBack/src/services" - destination-github-username: "RunOnFlux" - destination-repository-name: "fluxjsdocs" - user-email: runonfluxbot@gmail.com - target-branch: master - target-directory: services/ + GH_TOKEN: ${{ steps.docs-token.outputs.token }} + run: | + set -eo pipefail + CLONE=$(mktemp -d) + git clone --quiet --depth 1 --single-branch --branch master \ + https://github.com/RunOnFlux/fluxjsdocs.git "$CLONE" + # Replace rather than merge, so a file deleted here disappears there too. + rm -rf "$CLONE/services" + mkdir -p "$CLONE/services" + cp -a ZelBack/src/services/. "$CLONE/services/" + git -C "$CLONE" config user.email runonfluxbot@gmail.com + git -C "$CLONE" config user.name runonfluxbot + git -C "$CLONE" add -A services + if git -C "$CLONE" diff --cached --quiet; then + echo 'docs unchanged, nothing to push' + exit 0 + fi + git -C "$CLONE" commit --quiet -m "Update from https://github.com/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}" + git -C "$CLONE" push --quiet \ + "https://x-access-token:${GH_TOKEN}@github.com/RunOnFlux/fluxjsdocs.git" master From b5571ab9b6b7b41f51574be1280f72e8ca24860f Mon Sep 17 00:00:00 2001 From: David White Date: Tue, 25 Aug 2026 13:21:36 +0100 Subject: [PATCH 8/8] chore(ci): move the app-token action to v3 actions/create-github-app-token v1 -> v3, on both the dispatch and docs token mints. v1 targets Node 20, which the runners now force onto Node 24 with a deprecation warning on every run. Neither major break reaches us: v2 removed the underscore input spellings (app_id, private_key) and this workflow uses the hyphenated ones; v3 removed custom proxy handling and raises the self-hosted runner floor, and these jobs use GitHub-hosted runners with no proxy. app-id, private-key, owner and repositories are all still accepted. codecov/codecov-action stays on v3 deliberately. v4 moved to the CLI uploader and dropped tokenless uploading for non-fork runs, so bumping it needs a CODECOV_TOKEN secret first or coverage upload starts failing. Unrelated to this PR; left for whoever adds the token. Raised by Cabecinha84 on #1789 as a cosmetic note. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HQKpZbuxYqrWeErU7vKgoA --- .github/workflows/nodejs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 9233c47f00..1cfc50ae36 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -50,7 +50,7 @@ jobs: # anywhere and nothing to renew. - name: Mint the dispatch token id: dispatch-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v3 with: app-id: ${{ secrets.FLUXHASHES_APP_ID }} private-key: ${{ secrets.FLUXHASHES_APP_KEY }} @@ -130,7 +130,7 @@ jobs: - name: Mint the docs token if: github.repository == 'RunOnFlux/flux' && github.ref == 'refs/heads/development' id: docs-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@v3 with: app-id: ${{ secrets.FLUXJSDOCS_APP_ID }} private-key: ${{ secrets.FLUXJSDOCS_APP_KEY }}