Audience: the SAP GitHub org admin who owns
https://github.com/sap-tutorials, and thetutorials-imsrepo maintainer who will set the Actions secrets and flip the activation variable. This doc is intentionally short and copy-pasteable.
For the why behind this change, see github-app-migration.md. The short version: replace a long-lived classic PAT (which expires under SAP rotation policy and is tied to one human) with a GitHub App that mints a fresh 1-hour token per workflow run.
- Today:
tutorials-imsrunsnpm run fetch-tutorialsin CI using theTUTORIALS_GITHUB_TOKENsecret (a classic PAT). When the PAT expires, the build breaks until someone with access regenerates and re-pastes it. - After: the workflow asks GitHub for a short-lived installation token at the start of each run. Nothing to rotate.
The workflow change is already merged (see .github/workflows/rebuild-content.yml):
- A
Generate GitHub App tokenstep gated on the repo variableUSE_GITHUB_APP == 'true'. - The
Fetch tutorialsstep pickssteps.app-token.outputs.token || secrets.TUTORIALS_GITHUB_TOKEN.
So:
- Until the org admin completes setup → the variable stays unset → step is skipped → PAT continues to be used.
- Once secrets + variable are in place → App token is used automatically.
- To roll back at any time → set the variable to anything other than
true(or delete it).
No code changes required in scripts/parsers/github.ts. Installation tokens are standard Bearer tokens.
These run once. Estimated time: 15 minutes.
- Go to https://github.com/organizations/sap-tutorials/settings/apps/new
- Fill in:
- GitHub App name:
sap-tutorials-builder - Homepage URL:
https://github.com/sap-tutorials/tutorials-ims(or wherever the repo lives) - Webhook → Active: uncheck (we don't consume events)
- Repository permissions:
Contents: Read-only (Phase 3 note: bump to Read and write only if you migrateTUTORIALS_POC_DISPATCH_TOKEN— needed forrepository_dispatch)Metadata: Read-only (auto-selected)Actions: Read and write (runtimeworkflow_dispatchfrom the CAP app — Phase 2)Issues: Read and write (#2384 — lets the QA rebuild file a per-tutorial "source error" issue on the triggering-Contributionrepo, assigned to / @-mentioning the author, when a tutorial's markdown is missing/misnamed. The default jobGITHUB_TOKENis scoped totutorials-imsonly and cannot write issues cross-repo, so this must come from the App.)- everything else: No access
- Organization permissions: all No access
- Account permissions: all No access
- Where can this GitHub App be installed? Only on this account
- GitHub App name:
- Click Create GitHub App.
On the App's settings page, scroll to Private keys → Generate a private key. A .pem file downloads automatically. Save this file — you'll paste its contents into a repo secret in Part 2. The key cannot be recovered later; if lost, generate a new one and rotate.
At the top of the App's settings page: App ID: XXXXXXX. Copy this number.
- On the App settings page, left sidebar → Install App.
- Click Install next to
sap-tutorials. - Choose either:
- All repositories (simpler; covers public tutorials + private
*-Contributionrepos automatically as new ones are added), or - Only select repositories → pick every public tutorial repo plus every
*-Contributionprivate repo.
- All repositories (simpler; covers public tutorials + private
- Click Install.
- After installation, the URL contains
/installations/<INSTALLATION_ID>. Copy this number.
Send to whoever owns tutorials-ims Actions secrets:
- App ID (the number from 1.3)
- The full contents of the
.pemfile from 1.2 — paste between-----BEGIN RSA PRIVATE KEY-----and-----END RSA PRIVATE KEY-----inclusive - Installation ID (the number from 1.4)
Use a secure channel (e.g. SAP password vault, encrypted email, in-person). Treat the private key like any other credential — anyone with it can mint tokens with the App's permissions.
These also run once, after Part 1 is complete. Estimated time: 5 minutes.
Settings → Secrets and variables → Actions → New repository secret. Add:
| Name | Value |
|---|---|
TUTORIALS_APP_ID |
App ID from 1.3 |
TUTORIALS_APP_PRIVATE_KEY |
Full .pem contents from 1.2 (including BEGIN/END lines) |
TUTORIALS_APP_INSTALLATION_ID |
Installation ID from 1.4 (optional — see note) |
The
actions/create-github-app-token@v1action can resolve the installation ID fromapp-id + ownerautomatically. The current workflow does not passinstallation-id, so this secret is optional. Add it only if you later want to pin a specific installation.
Same page, Variables tab → New repository variable:
| Name | Value |
|---|---|
USE_GITHUB_APP |
true |
This is a non-secret variable on purpose — it's a feature flag, not a credential, and being non-secret means it shows up clearly in workflow logs.
- Go to Actions → Rebuild Content → Run workflow.
- Choose
dev, leaveslugempty. - Watch the run. You should see:
Generate GitHub App tokenstep: ✅ runs (was previously skipped)Fetch tutorialsstep: ✅ uses the App token (no observable difference in output)
- If the run succeeds end-to-end, the migration is complete.
Once you've confirmed at least one scheduled or dispatch-triggered run works without manual help:
- Delete the
TUTORIALS_GITHUB_TOKENrepo secret. - Revoke the underlying classic PAT in the source account's developer settings (so it can't be used elsewhere by accident).
Keep the
TUTORIALS_GITHUB_TOKENsecret for at least one full successful run cycle before deleting. It's the rollback path if the App turns out to have a permission gap.
The CAP app (tutorials-srv) mints its own installation token via
srv/lib/github-app-token.js for the rebuild dispatcher and the two fetch
crons. This needs the App secrets in the BTP Credential Store (not just
GitHub Actions) plus a runtime flag.
At /admin-ui/#secrets-display on the target env's approuter, set values for
the three registry rows (seeded by scripts/seed-secrets.cjs):
| Alias | Value |
|---|---|
TUTORIALS_APP_ID |
App ID from 1.3 |
TUTORIALS_APP_INSTALLATION_ID |
Installation ID from 1.4 |
TUTORIALS_APP_PRIVATE_KEY |
Full .pem contents from 1.2 (multi-line, incl. BEGIN/END) |
cf set-env tutorials-srv USE_GITHUB_APP true && cf restart tutorials-srvVerify: trigger an admin save (fires a debounced workflow_dispatch) and
confirm rebuild-content ran; check srv logs for [github-app-token] warnings
(none expected on success). Roll back with cf set-env tutorials-srv USE_GITHUB_APP false && cf restart tutorials-srv — the PAT path resumes.
Each *-Contribution repo fires repository_dispatch at tutorials-ims via
notify-qa.yml. To migrate off TUTORIALS_POC_DISPATCH_TOKEN:
- Ensure the App is installed on the Contribution repo and holds
Contents: write on
tutorials-ims(dispatch permission). - Add
TUTORIALS_APP_ID+TUTORIALS_APP_PRIVATE_KEYActions secrets to the Contribution repo. - Set repo variable
USE_GITHUB_APP=trueon the Contribution repo. - Push a tutorial change; confirm a
tutorial-qa-updateddispatch reachestutorials-ims. - After a clean run, delete
TUTORIALS_POC_DISPATCH_TOKENfrom that repo.
Roll out per-repo; each is independent.
When a slug-targeted QA rebuild can't fetch a tutorial's source markdown
(missing/misnamed file — e.g. tutorials/<slug>/architecture.md instead of the
required <slug>.md), rebuild-content-qa.yml skips that slug, keeps the job
green, and files a per-tutorial issue on the triggering -Contribution
repo, assigned to / @-mentioning the tutorial's author. That cross-repo issue
write needs the App to hold Issues: Read and write — the default job
GITHUB_TOKEN is scoped to tutorials-ims only.
For an already-registered App (the usual case — the App exists from Parts 1–4), granting a new permission is a two-step manual action by an org owner:
- App settings → Permissions & events → Repository permissions →
set
Issuesto Read and write → Save changes. - GitHub then marks the change pending on every installation. Re-accept
it: org → Settings → GitHub Apps →
sap-tutorials-builder→ Review request, and approve the new Issues permission. Until this is accepted the installation token still lacks Issues — the workflow step self-warns and the job stays green, but no author issue is filed.
The App installation must already cover the -Contribution repos (Part 1.4 /
Part 4 install "All repositories" or the specific Contribution repos). No token
or secret changes are needed — the workflow files the issue with the existing
TUTORIALS_APP_* token, gated on vars.USE_GITHUB_APP == 'true'.
Verify: re-trigger a QA rebuild for a deliberately-misnamed slug (or wait for
a real one). Expect the job to succeed with a ⚠️ Skipped (author source error)
line in the run summary, and a tutorial-source-error-labelled issue on the
source repo. If no issue appears, confirm the Issues permission was accepted
(step 2) and USE_GITHUB_APP=true.
If anything misbehaves after activation:
- Set
USE_GITHUB_APPrepo variable tofalse(or delete it). - Re-add
TUTORIALS_GITHUB_TOKENsecret if it was already deleted. - Re-run the workflow. It falls back to the PAT path automatically.
No code change needed.
| Symptom | Likely cause | Fix |
|---|---|---|
Resource not accessible by integration on a *-Contribution repo |
App not installed on that repo | Re-run install (1.4), pick "All repositories" or add the missing repo to the selected list |
Bad credentials on GraphQL |
Private key pasted incorrectly (missing BEGIN/END lines, line endings mangled) | Re-paste the .pem exactly as downloaded; GitHub's secret editor preserves newlines correctly when pasted whole |
app-token step is skipped even after activation |
USE_GITHUB_APP variable name typo, or value is not exactly true |
The check is vars.USE_GITHUB_APP == 'true' — must be lowercase string |
| Rate limit errors after migration | App installation rate limit (5000 req/hr) shared with other workflows | Unlikely with current build cadence; check if other workflows now use the same install |
github-app-migration.md— engineering rationale, current state analysis, comparison tableactions/create-github-app-token— the GitHub-published action used in the workflow- GitHub Apps documentation