Skip to main content
smartcloud talks to GitHub with tokens. A token is like a key card: whoever holds it can open the doors it was made for, and no others. smartcloud can hold up to three key cards at once, and it uses each one only for its own doors. This page explains the three, why they are kept apart, and how to set them up.
New to the words here? The workflow token, GitHub App, preset and restricted run are explained in the glossary.

Why split the tokens?

The simplest set-up gives smartcloud one strong token and lets it do everything with it. That works, but it means every run, including the run for a pull request whose workflow file the pull request itself can change, holds a key that can change repository settings, push workflow files and, if the token reaches it, write to your organisation’s shared .github repository. Splitting the tokens means each run holds only the keys it needs:
  • A pull request run never holds the strong token, so a changed workflow on a pull request cannot use it.
  • The strong token reaches only the repository it runs in, so a leaf repository can never write to your shared .github repository.
  • Reading your house preset needs only a read-only key to .github, so that key is all a pull request run gets.

The three tokens

When a run has no app token (every pull request run, in the house workflow), smartcloud runs restricted: it skips settings, and it skips sync’s scheduled proposal, and says so in the job summary. With a house token, the sync edit check on a pull request still runs, because it only reads.

Set it up

1

Create a GitHub App

Follow Create a GitHub App. Install it on each repository that runs smartcloud and on your organisation’s .github repository. Store its ID as the variable SMARTCLOUD_APP_ID and its private key as the secret SMARTCLOUD_APP_PRIVATE_KEY, at organisation level so every repository can use them.
2

Mint the read-only house token

Add a step that asks the app for a token limited to reading .github:
permission-contents: read is what makes it read-only: the token gets that one permission, whatever the app itself may do.
3

Mint the app token only where it is needed

Add a second step, limited to the repository itself, to runs from the default branch and to merged pull requests (for backports):
With neither owner nor repositories, the token reaches only the repository the workflow runs in. If your config extends a preset in another private repository that is not .github, add owner and list that repository in repositories too.
4

Pass all three to smartcloud

workflowToken is left out on purpose: it defaults to github.token. The || github.token fallback means a run with no app token acts with the workflow token and runs restricted instead of failing.

Why backport needs the app token

A backport pushes a branch and opens a new pull request. GitHub never starts workflows for a pull request opened with the workflow token, so a backport opened that way would sit with no CI, and its required checks would never pass. So backport acts with the app token too. The app token is minted for a pull request run only when the pull request has been merged and came from the repository itself. By then its code is on the default branch, so the run is trusted: nobody can change that run’s workflow file any more. Every other pull request run, and every comment run, still acts with the workflow token. If the app token is missing on that run (the mint failed, or the pull request came from a fork), the job’s workflow token has contents: read and cannot push. smartcloud then opens nothing and reports a warning, “#7 was not backported to v1: the token cannot push …”, in the job summary instead of claiming a backport it did not make.

A complete example

.github/workflows/smartcloud.yml
Pin each action to a full commit SHA in production.

What you will see

  • On a pull request: the checks, comment and labels come from github-actions[bot], the workflow token’s identity. The job summary has a notice, “ran with restricted access (the workflow token, without an app or access token)”, and lists settings as skipped. That is expected: settings never runs on pull requests anyway. Your house preset is read with the house token, so its rules are checked.
  • On a push, schedule or manual run: settings and sync act as your app (for example my-app[bot]); the check run and labels still come from github-actions[bot].
  • When a pull request with a backport <branch> label is merged: the backport pull request is opened by your app, so your CI runs on it.
  • On a pull request from a fork or Dependabot: no app token of either kind is minted, and the house token input is ignored even if one is passed. smartcloud acts with the workflow token alone, as before.

Options

Older workflows that pass one strong token as GITHUB_TOKEN and no houseToken keep working: the strong token then reads presets and runs settings and sync, and the workflow token still does everything in the repository.

Common problems

The run had no house token that could read .github. Check that the app is installed on .github, that the house token step ran (it is skipped for forks and Dependabot, which is expected), and that houseToken is passed to smartcloud.
The app token was not minted. Open the job log: the “Mint the app token” step either was skipped (check its if) or failed (check the variable and secret, and that the app is installed on the repository). A failed mint falls back to the workflow token rather than failing the run.
These act with the workflow token now, so the job’s permissions must grant them: checks: write, issues: write, pull-requests: write and statuses: read.
GitHub does not start workflows for events made with the workflow token. Labels and comments now come from the workflow token, so a workflow that listened for smartcloud’s labels needs another trigger, such as pull_request with synchronize.
Last modified on September 28, 2026