> ## Documentation Index
> Fetch the complete documentation index at: https://docs-stg.outerlayer.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub App permissions and build tokens

> What the OuterLayer GitHub App is allowed to do, and the short-lived repository tokens the gateway issues for a build.

The GitHub App reads your repository and posts results to it. For a build, the gateway also issues a short-lived token limited to the item's repository.

In a container build the runner asks for these tokens and holds them, so the build never does. See [the git remote and the build's tokens](/container-builds#the-git-remote-and-the-builds-tokens). A build that runs with hooks of your own fetches and pushes with the GitHub login already on the host. Every host build still opens its pull request as the App: see [Opening the pull request](/reference/build-reference#opening-the-pull-request).

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/outerlayer-stg/h46SL2WH_1X-gmFL/images/build-credentials-light.svg?fit=max&auto=format&n=h46SL2WH_1X-gmFL&q=85&s=c17763427080717202a5a11324dc249e" alt="A host build never holds a real key. Claude Code, git and gh, and the outerlayer CLI send their requests without one. The runner adds the host's Claude credential for Anthropic, a GitHub token that lasts about an hour and pushes to one branch, and an item key that works only on this work item for OuterLayer. With builtin:process or your own hooks, the build can read the item key." width="660" height="330" data-path="images/build-credentials-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/outerlayer-stg/h46SL2WH_1X-gmFL/images/build-credentials-dark.svg?fit=max&auto=format&n=h46SL2WH_1X-gmFL&q=85&s=9bb83b843c4350395b0af67e4f537e4a" alt="A host build never holds a real key. Claude Code, git and gh, and the outerlayer CLI send their requests without one. The runner adds the host's Claude credential for Anthropic, a GitHub token that lasts about an hour and pushes to one branch, and an item key that works only on this work item for OuterLayer. With builtin:process or your own hooks, the build can read the item key." width="660" height="330" data-path="images/build-credentials-dark.svg" />
</Frame>

## Permissions the App asks for

If GitHub's list for the App differs from this table, GitHub is right.

| Permission | Access | Why |
| - | - | - |
| Metadata | Read | GitHub requires it of every App. The App uses it to list the repositories an installation includes. |
| Contents | Read and write | Read files, commits and branches, including `.outerlayer/policy.yaml` and the validators. Write is used only by a push token, so a build can push the item's branch. |
| Issues | Read and write | Read issues for **Add work**. Post the evidence comment on a pull request, which GitHub treats as an issue comment. |
| Pull requests | Read and write | Read pull request state and reviews. Write is used to merge from the Work page, and to open or edit a build's pull request through `PUT /v1/work-items/{workItemId}/pull-request`. |
| Checks | Read and write | Post the evidence check run on each pull request. Read the results of CI that reports through checks. |
| Actions | Read | Read the results of GitHub Actions workflow runs. |
| Commit statuses | Read | Read the results of CI that reports through commit statuses. |
| Workflows | Write | Used only by a push token, and only when the repository's policy allows it. It lets a build change files under `.github/workflows/`. |

### Accepting a permission the App asks for

An installation keeps the permissions it accepted. When the App asks for more, GitHub does not widen the installation on its own. An owner of the installation must accept the request.

Until then, a token that needs a missing permission is refused with `repository_permissions_pending`. Everything else keeps working.

Opening or editing a build's pull request needs pull requests write (`pull_requests: write` on GitHub). A new installation gets it when it is installed. An older installation that never accepted it must accept it first. Until then, the route answers `repository_permissions_pending` and opens nothing.

To accept, open the installation's settings link that `outerlayer doctor` prints. On GitHub the request shows as **Review request**. One acceptance covers every repository in the installation.

### Check what each repository is missing

Run `outerlayer doctor` on any signed-in machine, with a factory key or a login. It adds one **GitHub App: owner/name** check per repository connected to the factory:

* It warns when the installation has not accepted a permission builds need, and names each one. The fix says who acts. A permission the App's own settings request is accepted by an owner of the installation, on the installation's settings page. A permission the App's settings do not request cannot be accepted yet: the App's settings must add it first. The response of `GET /v1/repositories/access` marks each as `requestedByApp`.
* It warns when the repository's default branch does not require a pull request. See [Protect the default branch](#protect-the-default-branch).
* It passes otherwise.

Builds need contents, pull requests and issues write, and actions, checks, commit statuses and metadata read. A permission held at read does not count where write is needed.

`doctor` only reads. It changes nothing on GitHub or in your factory. It asks the gateway with `GET /v1/repositories/access`, which any key holding `git.read` can call; the API reference has the response.

## Build tokens

For a build, the gateway issues short-lived GitHub tokens through the App. Each token covers one repository and lasts about an hour. Only the key that holds the item's live claim gets one, and only for a build a member of your organization asked for. A build that answers a merge conflict is the one exception. The rules, the request and every refusal are in [Repository tokens](/reference/build-reference#repository-tokens).

## Who a build's commits are authored by

Every commit an unattended build makes is authored and committed by the App's bot account, `<app-slug>[bot]`. GitHub links it to the App, so a reviewer can tell a factory commit from a person's. The runner sets the author for every process the build starts, and git prefers it to any identity configured on the host. The host owner's own git identity never appears on a factory commit.

The member who asked for the build is credited as co-author. Each commit message ends with a `Co-authored-by: <name> <email>` line for them, so the commit counts on their GitHub profile. The requester is the same person the [token rules](/reference/build-reference#who-can-get-a-token) use. For an implement build, it is whoever ran `outerlayer work build` or chose **Build** on the Work page. For an amend build, it is the author of the comment that started it. For an amend build that a merge conflict started, it is the member who asked for the item's newest build, if still active. Otherwise the commits carry no co-author line.

The co-author comes from the GitHub account the member signed in with, not from their profile:

* The email is `<github-user-id>+<login>@users.noreply.github.com`, built from that sign-in. A member cannot change it by editing their profile.
* The name is the member's profile name, or their GitHub login when the profile has none. The login is also used when the profile name is over 200 characters or holds a line break or an angle bracket, which a commit trailer cannot carry.
* A member who signed in without GitHub gets no co-author line. The commit is still authored by the bot.

Both addresses are GitHub `noreply` addresses. No personal email address is written into a commit.

This needs two things: a gateway that holds the App's keys, and a runner at protocol 8 or newer.

* A gateway without the App's keys sends no identity. Commits keep the host's own git identity.
* For an older runner, the gateway skips the bot lookup, because that runner ignores the identity. The claim succeeds even when GitHub cannot be reached.
* If GitHub rate limits the bot lookup, the claim answers 503 at once and the runner claims again.

The release of each attempt records the result as `environment.commitIdentity`: `author` is `app` or `host`, and `coAuthor` is `credited` or `none`. `GET /v1/work-items/{workItemId}` returns it on each claim.

The runner adds the co-author line with git hooks of its own. Your repository's own git hooks still run first, and a hook that refuses a commit still refuses it.

A build that a runner's own provision hook runs through an `exec` gets the bot as author and no co-author line. Its commands may run inside a virtual machine, where the runner's hooks directory does not exist. Commits from your own local session are not affected.

## Letting a build change workflows

A build cannot change files under `.github/workflows/` unless the repository allows it. Set this in `.outerlayer/policy.yaml`:

```yaml theme={"system"}
build:
  workflows: allow
```

The gateway reads the file from the repository's default branch. A change on the item's own branch does not count, so a build cannot grant itself the permission. See [Policy and validators](/policy-and-validators).

## Protect the default branch

A push token can write to any branch of the item's repository, and a push runs that repository's CI. Two settings keep a build from reaching further than its own branch:

* **Require a pull request before merging** on the default branch. Use a ruleset with the **Require a pull request before merging** rule, or classic branch protection with required reviews. A build then cannot push to the default branch directly; its change lands only through a pull request someone approves.
* **Keep CI secrets in protected environments.** Put deployment keys and other secrets in a GitHub environment whose deployment branches are limited to the default branch, and reference that environment from the workflow jobs that need them. A workflow run on a build's branch then gets no secret it could print or send.

`outerlayer doctor` warns about a default branch that requires no pull request. It reads rulesets with the installation's own access. Reading classic branch protection needs the Administration permission, which the App does not ask for. So for a branch protected the classic way, `doctor` cannot tell whether reviews are required, and says so instead of warning. A ruleset is readable either way.

## What the gateway records

For every token it issues, the gateway records which claim, repository, access and key it was for, and the permissions it asked GitHub for. It also writes a **Repository Token Issued** event to the organization audit log. It never stores the token.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.