App manifest with the submission form#
Use this form to register an app with the project’s required permissions and
events, when you set the credentials in the environment yourself. Registering
the app on the running site (Register the GitHub App on the site) is easier. You need a GitHub account allowed to register
the app, a browser, and a checkout with uv installed to save credentials.
For the other setup flows, return to Set up the GitHub App.
1. Prepare the site#
From the checkout:
make dev
test -f .env || cp .env.development.example .env
mkdir -p data
Set your app name and site address in .env. Use the same values in the form:
SITE_NAME=your-unique-development-app-name
SITE_URL=http://localhost:5000
Choose a distinct app name of at most 34 characters. For a deployed site,
use its public HTTPS address; see Run the site with Docker. Start make run in
another terminal so the site can receive GitHub’s registration return.
2. Submit the manifest#
Fill in the form and press Create app on GitHub. GitHub opens in a new tab, where you review the name and confirm Create GitHub App. Leave the organization empty for your personal account, or enter its GitHub login.
All URLs of the app are derived from your site URL: callback, installation,
registration return and the webhook, which is always the site URL plus
/github/webhook/. GitHub cannot deliver webhooks to localhost; to test
them locally, make the site public with Tailscale Funnel as in
Test the GitHub App locally with Tailscale and enter that address as site URL.
3. Save the return URL and registration state#
GitHub returns the new tab to your site with
codeandstatein the URL. Copy that whole return URL. The homepage currently does not exchange the code automatically.Return to this documentation tab and click Save registration state. Save the downloaded
github-app-state.txtasdata/github-app-state.txtin your checkout. Keep it unchanged and do not replace a state file from another registration that is still in progress.
If this tab is lost before you save the state, use a fresh registration attempt; the code-exchange check requires the state of the attempt you submitted. The form sends configuration to GitHub; it does not install the app or save credentials to your checkout.
4. Exchange the code and configure credentials#
Keep the saved state file at data/github-app-state.txt. The following
command checks it against the return URL before exchanging the code.
Within one hour of starting registration, run the next command. Paste the return URL at its hidden prompt. It checks the saved state, exchanges the code with GitHub and writes the private key and a settings fragment without printing their contents:
uv run python - <<'PY'
import getpass
import hmac
import os
from pathlib import Path
from urllib.parse import parse_qs, quote, urlsplit
import httpx
for filename in ("data/github-app.pem", "data/github-app-settings.env"):
if Path(filename).exists():
raise SystemExit("Credential files already exist; choose fresh filenames before conversion.")
returned = getpass.getpass("Paste the full manifest return URL: ")
parameters = parse_qs(urlsplit(returned).query)
expected = Path("data/github-app-state.txt").read_text()
states = parameters.get("state", [])
codes = parameters.get("code", [])
if len(states) != 1 or not hmac.compare_digest(states[0], expected):
raise SystemExit("The return state does not match this registration attempt.")
if len(codes) != 1:
raise SystemExit("The manifest return URL must contain one code.")
response = httpx.post(
"https://api.github.com/app-manifests/" + quote(codes[0], safe="") + "/conversions",
headers={"Accept": "application/vnd.github+json"},
timeout=20,
)
if response.status_code != 201:
raise SystemExit("GitHub rejected the conversion; check the code and one-hour deadline.")
credentials = response.json()
pem = credentials["pem"]
values = {
"GITHUB_APP_ID": credentials["id"],
"GITHUB_APP_SLUG": credentials["slug"],
"GITHUB_CLIENT_ID": credentials["client_id"],
"GITHUB_SECRET": credentials["client_secret"],
"GITHUB_WEBHOOK_SECRET": credentials["webhook_secret"],
"GITHUB_APP_PRIVATE_KEY_FILE": "data/github-app.pem",
}
os.umask(0o077)
with Path("data/github-app.pem").open("x") as output:
output.write(pem)
with Path("data/github-app-settings.env").open("x") as output:
output.write("".join(f"{key}={value}\n" for key, value in values.items()))
print("Saved data/github-app.pem and data/github-app-settings.env.")
PY
Open data/github-app-settings.env in your editor and copy its six settings
into .env, replacing any existing values for those keys. These files
contain credentials; data/ and .env are excluded from version control.
Keep the generated private key rather than generating another one on GitHub.
For Docker, mount the key as /app/data/github-app.pem and change
GITHUB_APP_PRIVATE_KEY_FILE to that container path. The response fields
map directly to the six environment settings shown in the example;
GITHUB_SECRET means the client secret, not the webhook secret or key.
5. Restart, sign in and install the app#
Stop the development server and run make run again so it reads the new
credentials. For Docker, restart the service with its updated environment.
There should be no ai_pycal.W001 warning about missing GitHub App settings.
Open http://localhost:5000/accounts/login/, sign in with GitHub, open Setup
and press Install the GitHub App. Select the repositories to include.
Registration creates the app; installation grants it access to repositories.
Continue with Set up a repository to add .github/agreements.yml and
activate a repository. Open a test PR and inspect the app’s recent webhook
deliveries on GitHub if it is not checked.
This form follows GitHub’s manifest registration examples.