Skip to main content
Labels are the coloured tags on GitHub issues and pull requests, such as bug or documentation. Keeping them tidy by hand is tedious: names drift, colours differ between repositories, and people forget to add them. The labels feature does three jobs for you:
  • Label sync. It makes the repository’s list of labels match the labels section of your config: it creates missing labels, fixes colours and descriptions, renames old labels, and (only if you ask) deletes labels you did not define.
  • Labelling. It adds a label to a pull request or issue while a rule’s conditions pass, and takes it off again when they stop passing. For example, “label every pull request that touches docs/** as documentation”.
  • Size labels. With a sizeLabels section, it adds built-in Size: XS to Size: XL labels to pull requests by how many lines they change.
It is turned on when the config has a labels, labelling or sizeLabels section. You can use any of the three on its own.

Turn it on

1

Add the workflow events

Label sync runs on repository events: push, schedule, workflow_dispatch, repository_dispatch and merge_group. Labelling runs on pull request and issue events. The workflow in Getting started listens to all of them. At minimum you need:
2

Grant the permissions

The job needs issues: write (repository labels, and labels on issues) and pull-requests: write (labels on pull requests). The workflow token is enough; no app or personal token is needed.
3

Define your labels

Add a labels section to .github/smartcloud.yml. Each entry has a key you choose, a name and a color:
The colour is six hexadecimal digits, as GitHub shows them. Quotes are optional: a colour made only of digits, such as 000000, is read back as the six digits you wrote. Quoting it (color: '000000') is still the clearest.
4

Add a labelling rule (optional)

Point a rule at the label’s key and give it a condition group:
5

Run it

Push to main (or run the workflow by hand) to sync the labels. Open a pull request that changes a file under docs/ to see the label applied.

Options

Keys of labels and labelling are any non-empty text without leading or trailing spaces, except __proto__.

Renaming a label with aliases

To rename bug to Type: Bug without losing it on existing issues, give the new name and list the old one as an alias:
The next sync renames the repository’s bug label in place. Every issue that had bug now shows Type: Bug. An alias is simply an old name. You keep it in the config so smartcloud still recognises the label wherever the old name is still in use. Before the sync has run. Label sync only runs on pushes and scheduled or manual runs, so a pull request can be labelled before the rename reaches the repository (always the case for a pull request from a fork, whose token cannot rename labels). Labelling knows about aliases, so it never leaves the label on twice:
  • If the rule for bug passes and the pull request still has defect, smartcloud adds Type: Bug and removes defect. The job summary shows added label "Type: Bug" to #7, replacing its old name "defect" and removed label "defect" (an old name of "Type: Bug") from #7.
  • If the pull request already has both Type: Bug and defect, smartcloud removes defect, so the label is on once.
  • If the rule for bug fails, smartcloud removes Type: Bug and defect, whichever are there.
  • If GitHub refuses to add Type: Bug (a read-only token), defect stays, so the pull request never loses the label. You get the usual labels.add warning.
For example, renaming the built-in Size: M to medium keeps Size: M as an alias for you (see Size labels), so a pull request that had Size: M ends up with medium only. When an alias is another label’s name. Current names win. If bug lists Type: Feature as an alias but another entry is called Type: Feature, labelling treats Type: Feature as that other label and never removes it as an old name of bug. The same holds for a name a labelling rule applies directly. When two entries list the same alias, the first entry in the config keeps it.

Several rules for one label

A label is on when any rule naming it passes. That lets you use one rule per subject kind:

Size thresholds

Change a threshold under sizeLabels.thresholds. Sizes you leave out keep their defaults. The thresholds must rise from s to xl, or the section is rejected:

Complete example

.github/smartcloud.yml

How it works

Label sync

Label sync runs on push, schedule, workflow_dispatch, repository_dispatch and merge_group events. It never runs on pull request events, because those from forks carry a read-only token. For each entry in labels:
  1. A repository label with the same name, ignoring case, is matched and updated when its name, colour or description differs.
  2. Failing that, a repository label named in aliases is renamed. Exact names are matched before aliases, so an alias never takes a label another entry names directly.
  3. Anything else is created.
Then:
  • A description left out of the config is left as it is on GitHub, so a config that only sets colours never blanks descriptions set by hand.
  • A label whose description is longer than 100 characters is reported as an error and left as it is. The rest still sync.
  • When two entries name the same label, ignoring case, only the first is synced, with a warning.
  • With labelSync.prune: true, repository labels the config does not define are deleted. A label named in an entry’s aliases counts as defined, even when the entry’s own name already exists.
v1’s skipDelete input deleted unconfigured labels unless it was set. v2 never deletes a label unless labelSync.prune is true.

Labelling

Labelling runs on pull request events (pull_request, pull_request_target, pull_request_review, pull_request_review_comment) and on issues events. It does not run on comments.
  • label is a key of labels, and that label’s name is applied. A value that is not a key is used as the name itself.
  • For each label, smartcloud checks every rule that names it and applies to the subject’s kind. The label is added when any of them passes, and removed when all of them fail.
  • Names compare ignoring case, as GitHub does.
  • A label still on the item under one of its aliases counts as present. When the label is wanted the old name is swapped for the current one, and when it is not the old name is removed. See Renaming a label with aliases.
  • A label that has already gone when smartcloud removes it is a warning, not a failure.
  • A label GitHub forbids adding or removing, as on the read-only token of a fork’s pull request, is a warning (labels.add or labels.remove), and the run carries on.
Labelling only touches labels some rule names. A label a person adds by hand, which no rule names, is never removed.

Size labels

sizeLabels adds five labels and a labelling rule for each, so every pull request carries one size label that moves as it grows or shrinks. Changed lines are additions plus deletions. Each label’s description states its range, for example “Changes 10 to 99 lines”.
  • The built-in labels and rules are ordinary labels and labelling entries, so label sync creates the labels and labelSync.prune counts them as defined.
  • A labels or labelling entry you write with the same key replaces the built-in one. Define labels.size-xl to rename or recolour XL, or labelling.size-xl to change when it applies. A renamed size keeps its built-in name among its aliases, so label sync renames the existing label instead of leaving the old one on pull requests.
  • The rules use the changesSize condition and apply to pull requests only.
To apply labels by hand from a comment, turn on the commands feature and use /label.

What you will see on GitHub

  • Labels page. After a sync, the repository’s labels (Issues, then Labels) match your config.
  • Labels on items. Labels appear on pull requests and issues as their rules pass, and disappear when they stop passing. The timeline shows them added by the account smartcloud runs as.
  • Check run. A smartcloud / labels check run on the commit. It succeeds when there is nothing wrong, is neutral with warnings (a repeated label name, a label already gone, a write refused), and fails on an error (an over-long description).
  • Job summary. Every label created, updated, renamed, deleted, added or removed is listed as a change.

Forks and restricted runs

On a pull request from a fork, or a Dependabot pull request, smartcloud acts with the read-only workflow token (see restricted runs). Labelling still evaluates its rules, but GitHub refuses the write: each refused add or remove becomes a labels.add or labels.remove warning and is listed under Restricted access in the job summary. Label sync never runs on pull request events, so it is not affected. To label fork pull requests, also run the workflow on pull_request_target, where GitHub gives the workflow token write access. The run is still restricted (smartcloud acts with the workflow token, never a stronger one), but that token can now write labels. That event runs your default branch’s workflow, and smartcloud reads the config from the default branch, so the fork cannot change the rules.

In the house preset

The house preset defines nine labels and nothing else from this page: the AI policy’s process labels needs-disclosure, needs-dco, needs-reproduction, needs-evidence, needs-tests, needs-human-explanation, needs-scope-approval and needs-refactor, and house-sync for its own sync pull requests. It sets no labelSync, labelling or sizeLabels. A Resnovas repository adds its own labels and rules below house:local under new keys. It cannot change the name, colour or description of a house label, and should not turn on labelSync.prune unless every label it wants to keep is in the config.

Troubleshooting

Label sync only runs on push, schedule, workflow_dispatch, repository_dispatch and merge_group. A pull request run does not sync labels. Push to the default branch or run the workflow by hand.
The colour is not six hexadecimal digits as written. Check for a typo, a missing digit or a colour name such as red. YAML reads some unquoted values as numbers, such as 1e3 or 0x1f; smartcloud keeps what you wrote, so these are reported rather than turned into a different colour. An all-digit colour such as 000123 is fine unquoted. When in doubt, put the colour in quotes: color: '0E8A16'.
GitHub rejects longer descriptions. Shorten labels.<key>.description to 100 characters or fewer.
Two entries have the same name, ignoring case. Remove or rename one of them.
The run’s token cannot write labels. See Forks and restricted runs.
Add the old name to the entry’s aliases. Without it, sync creates a new label and leaves the old one (and deletes it only with labelSync.prune: true, which drops it from every issue).
List the old name in the entry’s aliases. Labelling then removes the old name the next time it runs on that pull request (any new commit or edit), and the next label sync renames the repository label.
Each threshold must be larger than the one before it, counting the defaults for sizes you left out. For example, m: 5 fails because s is still 10.
A labelling rule names that label and its conditions fail, so smartcloud takes it off. Change the rule, or use a label no rule names.
Last modified on September 28, 2026