GitHub App setup
Install a GitHub App for Carol, fill [identity] with app_id, installation_id and acceptors, and point CAROL_APP_KEY at its private key.
Carol v0.1.22
The sync commands write with your own gh login. The engine loop writes as a GitHub App instead, so that Carol can tell its own labels and comments from a person's. When someone adds carol:accepted, Carol checks that a person did it, not its own bot.
You need the App for propose, specify, run, report, tick and watch. Without it, those commands refuse, and everything else works as before.
1 · Register and install the App
Register a GitHub App under your account or organisation, with these repository permissions and nothing else:
| Permission | Access | Used for |
|---|---|---|
| Issues | Read and write | Proposals, epics, labels, milestones, sub-issues, blocked-by edges, comments, and reading who added a label |
| Pull requests | Read and write | The PRD, run, design, findings, release and phase-review pull requests, reading reviews, merging |
| Contents | Read and write | Pushing carol/specify-<phase>, carol/run-<phase>, carol/design-<phase>, carol/findings-<ticket>, carol/release-v<version> and carol/review-<phase>, and the release tag |
| Workflows | Read and write | Pushing or merging a branch that adds or changes a file under .github/workflows/. Without it GitHub rejects the push ("refusing to allow a GitHub App to create or update workflow"), and a run that wrote a workflow is lost |
| Metadata | Read | Always granted |
Leave webhooks off: Carol polls, it doesn't listen. Then:
- Generate a private key for the App and save the
.pemfile outside the repository. - Install the App on the repository Carol manages.
- Note the App ID from the App's settings page, and the installation id from the installation's URL (
…/settings/installations/<id>).
The App writes as <app-slug>[bot]. Carol reads that login from GitHub itself, so the App can have any name.
Note
An App can't reach a Projects board owned by a user account. Carol puts items on the board with your own gh login instead, which needs the project scope (gh auth refresh -s project). The board is a view, so nothing in the loop depends on it.
2 · Add [identity]
Add an [identity] table to .carol/config.toml and commit it. It holds no secret.
[identity]
app_id = 1234567
installation_id = 89012345
acceptors = [201984135, 9184212]
| Field | Holds |
|---|---|
app_id | The App's numeric id |
installation_id | The id of its installation on this repository |
acceptors | The GitHub user ids of the people who may open a gate: accept a proposal, approve a gate pull request, answer questions, agree a contract |
acceptors are numeric ids, not logins, so a renamed account still counts. Look one up with:
gh api users/<login> --jq .id
Never list the App's own bot as an acceptor. Carol refuses to run if you do, because the bot could then accept its own proposals.
Without [identity], the App commands stop with config missing [identity] table. Any unknown key in the table is a config error.
3 · Point CAROL_APP_KEY at the key
The private key's path goes in the CAROL_APP_KEY environment variable, never in the config file or the repository:
chmod 600 ~/.config/carol/app.pem
export CAROL_APP_KEY=~/.config/carol/app.pem
The key file must have no group or other permission bits. 600 (owner read and write) or 400 (owner read) are fine. 644 fails carol doctor's app-key check. Put the export in your shell profile, or in the tmux window that runs carol watch.
When a command needs the App and CAROL_APP_KEY isn't set, it stops before reading anything: CAROL_APP_KEY is not set: <command> writes as the GitHub App and needs its private key.
For each command, Carol signs a short-lived token request with the key, using openssl, and exchanges it for an installation token. How long that token lasts is under Token lifetime.
4 · Check it with carol doctor
Once [identity] exists, carol doctor adds these checks:
$ carol doctor
…
ok openssl
ok zurdo-version
ok zurdo-skills
ok app-key
ok app-token
| Check | Passes when |
|---|---|
openssl | openssl is on your PATH |
zurdo-version | Zurdo is 1.30.0 or later, which specify needs for zurdo scaffold |
zurdo-skills | zurdo-prd-author is installed (zurdo skills install --all) |
app-key | CAROL_APP_KEY names a readable file with no group or other permission bits |
app-token | Carol can mint an installation token and read the App's bot, and the bot isn't an acceptor |
--format json gives the same checks as an envelope, with the bot's login and id on app-token.
Token lifetime
GitHub expires an installation token one hour after it's minted. A zurdo run or an agent session can take longer than that, and watch keeps one process running for as long as you leave it, so Carol ages each token from the moment it was minted and mints a new one once it is 45 minutes old. It checks:
- before each transition of a tick, and before its digest, since an earlier transition may have run for a long time;
- before every push or tracker write that follows a run or an agent session.
The remote URL for a push is built at push time, with the current token. Each new token prints one notice on stderr, such as carol: minted a new installation token (the last was 52 minutes old).
To change the age, set CAROL_TOKEN_MAX_AGE_SECS to a number of seconds. Keep it under 3600: a token older than an hour no longer works. A value Carol can't read as a number is ignored.
export CAROL_TOKEN_MAX_AGE_SECS=1800 # mint a new token every 30 minutes
What the App may do
The App writes issues, labels, comments and pull requests, pushes Carol's own branches, and merges a gate pull request once its approvals are met. Agent sessions that watch starts get no GitHub token at all: Carol pushes their commits and makes every GitHub write itself, as the App.
Next: Contracts