OSPO, used throughout this file, is Microsoft's Open Source Programs Office — the team that, with 1ES, runs the
microsoftopensourceGitHub enterprise and the official Microsoft organizations. They own themicrosoftorg, so they are the only ones who can accept an App transfer into it, install an App on its repositories, and approve later permission changes. Their queue ismicrosoft/github-operations.
.github/workflows/update-parameter-docs.yml regenerates three pages from every
new Z3 release and opens a pull request with the result:
| Page | Generated by |
|---|---|
website/docs-programming/04 - Parameters.md |
scripts/generate-parameter-docs.py (from the released z3 binary) |
website/docs-smtlib/03 - strategies/06 - summary.md |
doc/mk_tactic_doc.py in the Z3 release sources |
website/docs-smtlib/03 - strategies/07 - simplifiers-summary.md |
doc/mk_tactic_doc.py in the Z3 release sources |
The workflow cannot open that pull request with the built-in GITHUB_TOKEN:
organization policy blocks Actions from creating pull requests. The two
alternatives are a contributor-owned PAT or a GitHub App:
- A fine-grained PAT now expires after ~7 days and has no renewal API, so the secret lapses and the workflow breaks silently.
- A classic
repoPAT is wildly over-privileged — write access to every repo the owning account can push to — and is tied to one person. - A GitHub App mints short-lived, per-run installation tokens scoped to exactly the repos it is installed on, owned by the org rather than by a person, with nothing to rotate on a schedule.
This mirrors z3prover-ci-bot, the App already doing the same job for
Z3Prover/z3 and
Z3Prover/bench
(see docs/github-app/README.md in Z3Prover/bench).
| Permission | Access | Why |
|---|---|---|
| Metadata | Read-only | mandatory |
| Contents | Read & write | push the automation/update-z3-parameters branch |
| Pull requests | Read & write | open / update the parameter-update pull request |
No Issues permission (the workflow has no issue fallback), no account or organization permissions, no webhook.
Visibility must be public ("Any account"). This is counter-intuitive for a
single-repo automation identity, but OSPO's config-as-code review bot validates
the App through GET /apps/{slug}
(src/review-pr.ts),
and that endpoint returns 404 for a private App — even to its own owner. A
private App is rejected with "The app slug could not be validated as a public
GitHub App." The already-approved z3prover-ci-bot is public for the same
reason. Public only means other accounts may install it; every installation
still requires that account's admin to approve, and the App carries no secrets.
Installed on one repository: microsoft/z3guide.
The workflow narrows the minted token even further with
permission-contents: write / permission-pull-requests: write and
repositories: z3guide, so a run can never use more than it declares.
| File | Purpose |
|---|---|
manifest.json |
the App manifest — name, permissions, no webhook |
create-app.html |
open in a browser to create the App from the manifest in one click; keep in sync with manifest.json |
z3-guide-app.install.yml |
the config-as-code install file to PR into microsoft/github-operations at apps/microsoft/z3-guide-app.yml |
ospo-transfer-request.md |
ready-to-submit answers for the OSPO transfer ticket (step 2 below) |
Done —
z3-guide-app, App ID4662589, owned by@levnach. Kept here as the record of how it was created and what it must look like if it is ever recreated. Still outstanding from this step: Generate a private key.
Microsoft OSPO asks you to create the App on a personal account first and then request the transfer to the org.
Sign in to github.com in the browser you use for this. If you are signed out,
GitHub discards the manifest and the App form reports
Invalid GitHub App configuration — "url" wasn't supplied. That message means
signed out, not bad manifest: posting the manifest in manifest.json
without a session reproduces it exactly. Open
https://github.com/settings/apps/new — if it shows the Register new GitHub
App form you are signed in; if it shows a sign-in page, sign in first.
Go to https://github.com/settings/apps/new and fill in:
- GitHub App name:
z3-guide-app(if taken, append-2) - Homepage URL:
https://github.com/microsoft/z3guide— this is the field the manifest callsurl - Identifying and authorizing users: leave Redirect URI blank and leave Request user authorization (OAuth) during installation unchecked; the App never acts on behalf of a user
- Post installation → Setup URL: blank
- Webhook: untick Active (leave Webhook URL blank)
- Repository permissions:
- Contents → Read and write
- Pull requests → Read and write
- (Metadata → Read-only is added automatically)
- Subscribe to events: none
- Where can this GitHub App be installed? → Any account (see the visibility note above; OSPO's review bot cannot see a private App)
- Click Create GitHub App
Open create-app.html in the browser while signed in, click the button,
review the pre-filled confirmation, and click Create GitHub App.
App names are globally unique. If
z3-guide-apphad been taken, thenamein bothmanifest.jsonandcreate-app.htmlwould need to change, andz3-guide-app.install.ymlwould need renaming to match — OSPO's automation derives the App slug from that file name.
Then click Generate a private key (saves a .pem) and copy the
Client ID (Iv23li…) and the numeric App ID.
The permission checkboxes are easy to miss on the create form, and an App with
no permissions fails only much later, at the first real run. Check what GitHub
actually recorded — this needs nothing but openssl and the .pem:
cat > /tmp/appjwt.sh <<'SH'
set -euo pipefail
PEM="$1"; APP_ID="$2"
b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
now=$(date +%s)
header=$(printf '{"alg":"RS256","typ":"JWT"}' | b64url)
payload=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' $((now-60)) $((now+540)) "$APP_ID" | b64url)
signing_input="${header}.${payload}"
sig=$(printf '%s' "$signing_input" | openssl dgst -sha256 -sign "$PEM" -binary | b64url)
printf '%s.%s' "$signing_input" "$sig"
SH
JWT=$(bash /tmp/appjwt.sh /path/to/z3-guide-app.*.private-key.pem 4662589)
curl -s -H "Authorization: Bearer $JWT" https://api.github.com/app \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(d['slug'], d['client_id'], d['permissions'])"A correct registration prints:
z3-guide-app Iv23li6lyE3PHafpr9qb {'contents': 'write', 'metadata': 'read', 'pull_requests': 'write'}
{} means no permissions were saved — fix them at
https://github.com/settings/apps/z3-guide-app/permissions and press Save
changes. A successful call also proves the private key matches the App.
Check the webhook too. The create form happily accepts the Homepage URL as the Webhook URL and leaves the hook active, which produces a perpetual stream of failed deliveries and contradicts the no-webhook posture OSPO reviews:
curl -s -H "Authorization: Bearer $JWT" https://api.github.com/app/hook/configThis must return 404 (Not found). If it returns a url, untick Active
in the Webhook section of https://github.com/settings/apps/z3-guide-app,
clear the URL, and save. Do this before the transfer in step 2 — once the
App belongs to the org you may not be able to edit its settings until an org
owner grants App-Manager rights.
GET /app/hook/config reports only the stored configuration; it has no
active field. GET /app/hook/deliveries is the complementary check — it
returns 404 once the webhook is disabled, and lists deliveries while it is
live. Treat the pair as the test: both 404 means no webhook.
Done — filed as microsoft/github-operations#1726 and accepted; the App is now owned by the
microsoftorg.
Open a
Transfer and configure a first-party GitHub App
issue on microsoft/github-operations.
ospo-transfer-request.md holds the exact answers to
paste into that form; the short version is:
- GitHub app name:
z3-guide-app - Transfer to:
microsoft(the org that ownsz3guide). OSPO routes Apps created purely to work around reduced PAT lifetimes tomicrosoftengineeringinstead — if they ask for that, first flip the App to Any account under Advanced in the App settings, otherwise it cannot be installed onmicrosoft/z3guidefrom a different owning org. - App type: first-party app used for internal scenarios, engineering, development
- Side effects: it opens a pull request on the repos it is installed on, so it should be installed on selected repositories only — never org-wide
- Initial App Managers: the maintainers who will hold the private key
After the ticket is filed, initiate the transfer from App settings → Advanced → Transfer ownership.
Merged as microsoft/github-operations#1727; their review bot reported
Valid: 1 | Warnings: 0 | Invalid: 0. The installation itself is applied asynchronously by OSPO — see the status block at the end of this file for how to poll for it.
z3-guide-app.install.yml already carries the real Client ID and App ID. Open a
pull request on microsoft/github-operations adding it as
apps/microsoft/z3-guide-app.yml. OSPO's automation installs the App on the
listed repositories.
Do this after step 3. Setting the variable and secret before the App is actually installed satisfies the preflight guard, so the workflow stops skipping and starts failing at the token step instead — the exact failure mode the guard exists to prevent.
gh variable set Z3GUIDE_APP_CLIENT_ID --repo microsoft/z3guide --body "Iv23li6lyE3PHafpr9qb"
gh secret set Z3GUIDE_APP_PRIVATE_KEY --repo microsoft/z3guide < z3-guide-app.*.private-key.pemUntil both exist the workflow's preflight job emits a warning and skips the
run instead of failing, so an unconfigured repository does not produce a red
build every night.
Run the workflow manually with force enabled:
gh workflow run "Update Z3 parameter documentation" --repo microsoft/z3guide -f force=trueThe resulting pull request must be authored by app/z3-guide-app.
- Org owners (via Microsoft OSPO /
microsoft/github-operations) are the only role that can change the installed repositories, transfer, or delete the App, and the role that must approve permission changes. - App Managers can edit App settings (keys, name, requested permissions) once an org owner grants the team App-Manager rights. Until then, route setting changes through an OSPO ticket.
Keys never expire; rotate roughly yearly, or immediately if the .pem leaks:
- App settings → Private keys → Generate a new key (downloads a
.pem). gh secret set Z3GUIDE_APP_PRIVATE_KEY --repo microsoft/z3guide < newkey.pem- Re-run the workflow with
force=trueand confirm it is green. - Delete the old key in App settings.
Treat the .pem like a root credential: never commit it, never paste it into
logs. It is the only long-lived secret behind the docs automation.
- Edit
apps/microsoft/z3-guide-app.ymlinmicrosoft/github-operations(source copy:z3-guide-app.install.ymlhere) and open a pull request. - After it merges, update the
repositories:list of thecreate-github-app-tokenstep in every workflow that needs the new repo.
Edit the App's permissions in settings (or via OSPO), update the
expected_permissions value in the install config, and keep the
permission-* inputs on the create-github-app-token step in sync. GitHub then
requires an org owner to approve the new permissions before they take
effect.
| Symptom | Cause |
|---|---|
| Workflow skipped with a "not configured" warning | vars.Z3GUIDE_APP_CLIENT_ID or secrets.Z3GUIDE_APP_PRIVATE_KEY is missing (step 4) |
The 'client-id' … input must be set to a non-empty string |
the same missing variable/secret, on an older workflow without the preflight guard |
Invalid GitHub App configuration — "url" wasn't supplied when creating the App |
you are signed out of github.com in that browser; GitHub drops the manifest on anonymous requests (step 1) |
401 / Repository not found |
private key mismatch (rotate and re-set the secret), or the repository is not part of the installation (step 3) |
Resource not accessible by integration |
the App registration has no permissions; GET /app returns {} — set Contents and Pull requests to write and save (step 1) |
Failed ping deliveries under Advanced → Recent Deliveries |
a webhook is active, usually with the Homepage URL pasted in as the Webhook URL; untick Active and clear the URL (step 1) |
| OSPO PR bot: "The app slug could not be validated as a public GitHub App" | the App is private; GET /apps/z3-guide-app returns 404. Use Make public under Advanced (step 1) |
GitHub Actions is not permitted to create or approve pull requests |
the PR step is using GITHUB_TOKEN instead of the App token |
To tell a genuinely malformed manifest apart from a signed-out browser, inspect
what the form would send instead of trusting the error text: open
create-app.html and run this in the browser console.
JSON.parse(new FormData(document.getElementById('f')).get('manifest'))If it returns an object with a url field, the manifest is fine and the error
is the missing session.
This file. Update the permission table, the installed-repository list and the setup steps whenever the App ID, installed repos, permissions, or secrets change.
Live. Created on 2026-08-20 as z3-guide-app, now owned by the microsoft
org and installed on microsoft/z3guide.
- App ID:
4662589 - Client ID:
Iv23li6lyE3PHafpr9qb - Private key: generated; held outside the repository
- Permissions:
contents=write, metadata=read, pull_requests=write - Webhook: none —
GET /app/hook/configandGET /app/hook/deliveriesboth return404 - Visibility: public ("Any account") — required so OSPO's review bot can
resolve the App via
GET /apps/z3-guide-app - Transfer ticket: microsoft/github-operations#1726
— accepted;
GET /appreportsowner: microsoft (Organization) - Install request: microsoft/github-operations#1727
— merged; review bot reported
Valid: 1 | Warnings: 0 | Invalid: 0 - Installation: id
155278905,repository_selection: selected, covering exactly one repository,microsoft/z3guide - Credentials:
Z3GUIDE_APP_CLIENT_ID(variable) andZ3GUIDE_APP_PRIVATE_KEY(secret) are set onmicrosoft/z3guide
Verified end to end by minting an installation access token and listing the
repositories it grants — total_count: 1, microsoft/z3guide — which is the
same path actions/create-github-app-token takes in the workflow.
Live and verified end to end. #258
is merged, so the nightly run is active. A workflow_dispatch run on
2026-08-20 (32411177729)
passed both jobs and opened
#259 as app/z3-guide-app,
which is the whole chain working: App token minted, branch pushed, pull request
authored by the App.
The currency check named exactly the two stale pages and left the current one alone, as intended:
Regenerating because 'website/docs-smtlib/03 - strategies/06 - summary.md' does not reference z3-5.1.0.
Regenerating because 'website/docs-smtlib/03 - strategies/07 - simplifiers-summary.md' does not reference z3-5.1.0.
Outstanding:
- Merge #259. It adds the
Generated frommarker to06and07and picks up an upstream wording fix. Once it lands, all three pages carry the marker and the check reports "All generated pages already reference z3-5.1.0; nothing to do." — so the job stops regenerating daily and only acts on a genuinely new Z3 release.
Merging the config-as-code request does not install the App by itself. The in-repo installer workflow (
.github/workflows/enterprise-apps.yaml_ignore) is archived — "FUNCTIONALITY MOVED ELSEWHERE" — so OSPO applies installs asynchronously and there is no public workflow run to watch. PollGET /repos/microsoft/z3guide/installation(404not yet,200installed) rather thaninstallations_countfromGET /app.