This repository is a standalone GitHub label management repo for an organization.
Its job is to:
- keep this repo's label config in sync with the labels currently defined on the repo
- validate config changes automatically
- sync the resulting label set across the rest of the organization
- remove an exact label from issues and pull requests across the filtered repository set
This repo uses four workflows:
Config-Label_SyncValidate-ConfigsOrg-Label-SyncRemove-Labels
The normal flow is:
- You run
Org-Label-Syncmanually. - It first calls
Config-Label_Sync. Config-Label_Syncreads the labels on this repository and rewritesconfig/labels.jsoncso the file matches the repo's current managed labels.- If that file changed,
Config-Label_Synccommits and pushes the update. - That config change triggers
Validate-Configs. Org-Label-Syncthen checks out the latest default branch, validates the config again, and syncs labels across the organization.
.
|-- .github/
| `-- workflows/
| |-- config-label-sync.yml
| |-- org-label-sync.yml
| |-- remove-labels.yml
| `-- validate-configs.yml
|-- config/
| |-- auto-pruned-labels.jsonc
| |-- labels.jsonc
| |-- properties.jsonc
| `-- repository-filter.jsonc
`-- scripts/
|-- export-properties.mjs
|-- sync-config-labels.mjs
|-- sync-labels.mjs
`-- lib/
`-- config-utils.mjs
All config files live under config/ and use jsonc, so they can include commented examples at the top.
This is the general admin config for values that will differ between forks.
Fields:
organization: the GitHub organization to synclabelSyncTokenSecretName: the name of the GitHub Actions secret containing the sync tokensourceRepository: the repo whose labels are treated as the source forConfig-Label_SyncdeleteMissingByDefault: whether org sync should delete labels that are neither managed nor auto-pruned
Example:
This is the managed label set that gets created or updated across the org.
It is normally maintained automatically by Config-Label_Sync, based on the labels currently present on this repository after excluding auto-pruned labels.
Each label object uses:
namecolordescription
Example:
[
{
"name": "priority: high",
"color": "b60205",
"description": "Top-priority work"
}
]This is the list of labels that should always be removed from synced repositories.
The starter file is prefilled with GitHub's default labels:
bugdocumentationduplicateenhancementgood first issuehelp wantedinvalidquestionwontfix
If any of those labels exist on this repo, Config-Label_Sync excludes them from labels.jsonc. If they exist on target repos, Org-Label-Sync deletes them.
This controls which repositories Org-Label-Sync will target.
The file uses:
useWhitelist: whentrue, only repositories inwhitelistare synced; whenfalse, all discovered org repositories are synced except those inblacklistwhitelist: repos to include when whitelist mode is enabledblacklist: repos to exclude when whitelist mode is disabled
useWhitelist defaults to false, so blacklist mode is the default behavior.
Entries in either list can be either:
repo-nameowner/repo-name
Example:
{
"useWhitelist": false,
"whitelist": [
"sandbox-repo",
"your-org-name/important-repo"
],
"blacklist": [
"do-not-touch",
"your-org-name/private-internal-tools"
]
}File: .github/workflows/config-label-sync.yml
Trigger:
- manual via
workflow_dispatch - callable from other workflows via
workflow_call
What it does:
- Checks out the default branch
- Loads shared settings from
config/properties.jsonc - Reads the current labels on the source repository
- Removes any labels listed in
config/auto-pruned-labels.jsonc - Rewrites
config/labels.jsoncso it exactly matches the remaining labels - Commits and pushes the change if the config was updated
This workflow is the bridge between "the labels on this repo right now" and "the managed config we sync elsewhere."
File: .github/workflows/validate-configs.yml
Trigger:
- runs automatically on
pushtoconfig/** - runs automatically on
pull_requestchanges toconfig/**
What it does:
- Checks out the repo
- Runs
node scripts/validate-configs.mjs
Validation includes:
- JSONC parsing
- required property checks
- duplicate label detection
- repository filter shape and
useWhitelistvalidation - duplicate whitelist and blacklist detection
- invalid colors
- invalid repo names
- overlap detection between
labels.jsoncandauto-pruned-labels.jsonc - validation for the shared config used by
Remove-Labels
File: .github/workflows/org-label-sync.yml
Trigger:
- manual via
workflow_dispatch
Inputs:
dry_run: preview changes without writing themdelete_missing: overridedeleteMissingByDefaultfor the runrepositories: optional comma-separated subset of repositories afterrepository-filter.jsoncis applied
What it does:
- Calls
Config-Label_Sync - Checks out the latest default branch
- Loads shared settings from
config/properties.jsonc - Validates the updated config
- Discovers repos in the configured organization
- Applies
config/repository-filter.jsonc - Creates or updates labels from
config/labels.jsonc - Deletes labels listed in
config/auto-pruned-labels.jsonc - Optionally deletes any other unmanaged labels if
delete_missingordeleteMissingByDefaultis enabled
File: .github/workflows/remove-labels.yml
Trigger:
- manual via
workflow_dispatch
Inputs:
run_on_issues: remove the label from matching issuestarget_only_closed_issues: whenrun_on_issuesis enabled, only target closed issuesrun_on_pull_requests: remove the label from matching pull requeststarget_only_closed_pull_requests: whenrun_on_pull_requestsis enabled, only target closed pull requestslabel_name: exact label name to remove
What it does:
- Checks out the latest default branch
- Loads the org name and token secret name from
config/properties.jsonc - Validates the shared config inputs used for repo discovery
- Discovers repositories in the configured organization
- Applies
config/repository-filter.jsoncin whitelist or blacklist mode - Removes the exact label from the selected issues and/or pull requests in every remaining repository
Notes:
- GitHub Actions does not currently support conditionally hiding or nesting
workflow_dispatchinputs, so the closed-only toggles can be described as dependent but not visually tucked under their parent checkboxes.
Create a repository secret whose name matches labelSyncTokenSecretName in config/properties.jsonc.
That token needs enough access to:
- read and update labels on the source repository
- discover repositories in the organization
- read and update labels on target repositories
- push config updates back to this repository when
Config-Label_Syncchangeslabels.jsonc
- Fork or clone this repository into the organization you want to manage.
- Create the sync token secret in the repo.
- Update
config/properties.jsoncfor your org and repo. - Adjust
config/auto-pruned-labels.jsoncif you want a different always-delete list. - Configure
config/repository-filter.jsoncfor blacklist mode or whitelist mode. - Set the labels on this repository to the label set you want to manage.
- Run
Config-Label_Synconce if you want to populateconfig/labels.jsoncimmediately. - Run
Org-Label-Syncto propagate the labels across the organization.
labels.jsoncstarts empty until you define or sync labels on this repo- all repos in the org are targeted unless excluded by
repository-filter.jsonc - GitHub default labels are auto-pruned by default
- deleting unmanaged labels is off by default unless you enable it
{ "organization": "your-org-name", "labelSyncTokenSecretName": "LABEL_SYNC_TOKEN", "sourceRepository": "your-org-name/label-sync", "deleteMissingByDefault": false }