feat(ci): sync the label taxonomy to the GitHub mirror automatically #125
No Reviewers
Labels
Clear labels
Area/CI
Area/Completions
Area/Components
Area/Config
Area/Docs
Area/Functions
Area/Integrations
Area/Prompt & Theme
Area/Scripts
Area/Tests
Compat/Breaking
Kind/Bug
Kind/Chore
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Performance
Kind/Refactor
Kind/Security
Kind/Testing
good first issue
help wanted
.github/workflows and repository automation
completions/ - tab-completion scripts
The opinionated-component system (C1-C6)
config.fish and conf.d/ - startup and environment
docs/manual/ and the generated manual, man page, and site
functions/ - user-facing functions and helpers
integrations/ - third-party tool wiring
themes/ and prompt appearance
scripts/ - standalone helper scripts
tests/ - the syntax lint and functional suite
Breaking change that won't be backward compatible
Something is not working
Tooling, dependencies, and housekeeping
Documentation changes
Improve existing functionality
New functionality
Makes existing behavior faster or lighter
Restructures code without changing behavior
This is security issue
Issue or pull request related to testing
Priority
Critical
1
The priority is critical
Priority
High
2
The priority is high
Priority
Low
4
The priority is low
Priority
Medium
3
The priority is medium
Reviewed
Confirmed
1
Issue has been confirmed
Reviewed
Duplicate
2
This issue or pull request already exists
Reviewed
Invalid
3
Invalid issue
Reviewed
Won't Fix
3
This issue won't be fixed
Status
Abandoned
3
Somebody has started to work on this but abandoned work
Status
Blocked
1
Something is blocking this issue or pull request
Status
Need More Info
2
Feedback is required to reproduce issue or to continue work
Well-scoped and self-contained; a good place to start
Maintainer is looking for someone to pick this up
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Assignees
rootiest (Rootiest)
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: rootiest/fish-config#125
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Automates the manual label sync that #124 documented, so the taxonomy stays
aligned across both forges without anyone remembering to do it.
scripts/sync-labels.py— reads labels from Gitea, makes GitHub match.Stdlib only, so the CI step installs nothing beyond
python3..github/workflows/sync-labels.yml— runs it daily at 06:00 UTC, onany push to
maintouching the script or the workflow, and on manualdispatch (with a
dry_runinput).CONTRIBUTING.md— the mirror section said a label "must be created onthe mirror too — no automation does it for you", which is now false.
Rewritten to describe what the sync does and the token it needs.
Gitea stays the source of truth. Manage labels in its UI as you do now; the
mirror follows.
How it works
Colors and descriptions are normalized before comparison — Gitea returns
colors bare (
00838f), GitHub sometimes with a leading#, and adescription may be
nullon one side and""on the other. Without that,a steady state would rewrite all 33 labels on every run instead of being a
true no-op.
Two guards matter more than the happy path:
running unattended must not be able to strip a label off someone's issue.
One that's in use is reported with its count and left for a human.
a bad token, a moved repo — treating that as truth would propose deleting
every label on the mirror.
The job is gated with the same
github.server_url != 'https://github.com'check
ci.ymluses. Without it, the mirror would queue this forever againstracknerd-mini, the failure #120 fixed.Scope note
Deliberately one-way, Gitea → GitHub. A label edited directly on GitHub is
treated as drift and corrected on the next run; that side isn't a management
surface.
Notes
Gitea reads as delete-plus-create: the new name is created, the old pruned
only if unused. The two forges share no stable label ID, so nothing better
is available. Documented in the script header and in
CONTRIBUTING.md.Priority/,Reviewed/, andStatus/doesn't survive the trip. Unchangedby this PR, still true, still documented.
--dry-runpath works without a token, using GitHub's unauthenticatedread quota, so anyone can preview a sync.
Action required before this can run
Create a fine-grained GitHub PAT scoped to
rootiest/fish-configonly,with Issues: read and write (GitHub files the labels API under Issues)
and Pull requests: read (so the in-use check sees labels on PRs). Add it
to this repo's Actions secrets as
GH_MIRROR_TOKEN.The job fails with an explicit
::error::naming the secret and its scopesif it's absent, rather than silently doing nothing.
Verification
All mutation paths were exercised end-to-end against the live repos by
introducing deliberate drift on Gitea and then reverting it. Both forges are
back to an identical 33-label set.
python3 scripts/sync-labels.py --self-test→14 checks passed. Covers identical sets, cosmetic-vs-real drift, create/extra classification by direction, the empty-source case, URL encoding ofArea/Prompt & ThemeandReviewed/Won't Fix, and stable output ordering.--dry-runat steady state →no changes -- the mirror already matches Gitea. A correct implementation must find nothing to do here, which is the sharpest available test.Kind/Choreto#ff00ffwith a changed description and added a temporaryZZ/Sync Testlabel on Gitea → dry run reported exactlywould create ZZ/Sync Testandwould update Kind/Chore (color 90a4ae -> ff00ff, description).Kind/Chore #90a4aeand noZZ/Sync Testafter the run above.Kind/Chore #ff00ffwith the new description andZZ/Sync Test #123456on GitHub, confirmed via the API.Kind/Choreand deletedZZ/Sync Teston Gitea → run reportedupdate Kind/Chore (color ff00ff -> 90a4ae, description)anddelete ZZ/Sync Test (unused), and GitHub matched.no changes.GH_MIRROR_TOKENand without--dry-runexits 1 with the message naming the secret and its required scopes.python3 -c "import yaml; yaml.safe_load(...)"onsync-labels.ymlparses; triggers arepush,schedule,workflow_dispatchand the four steps are in the intended order.fish tests/run-tests.fish→10/10 passed;python3 docs/verify-manual.py→74/74 passed.CONTRIBUTING.mdanchors still resolve, and the stale "no automation does it for you" claim is gone.KEPTrather than deleting, then removing the issue.GH_MIRROR_TOKENto the Actions secrets (see above).dry_run: trueand confirm it reports no changes; then dispatch it normally and confirm a green run with the summary table in the job output.