learningequality / learningequality/.github
Consolidate shared workflows into a single generated automation.yml entry point
@akolson is already working on this.
Since Aug 25, 2026.
- Dominant language
- JavaScript
- Stars
- 1
- Forks
- 7
- PR merge metrics
- No merged PRs in 30d
Description
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Overview
Replace the per-feature call-*.yml caller workflows that every consuming repo must copy with a single automation.yml reusable workflow in this repo, fronted by one thin, generated caller each repo copies once. All event routing and on/off control moves centrally into .github; consumers carry only an exhaustive on: block and a single uses: job.
Complexity: High
Target branch: main
Context
Repos that consume our shared automations (studio, kolibri, …) copy ~7 caller workflows into .github/workflows/, each with its own on: trigger and exhaustive types: list, each uses:-ing a reusable workflow:
| Caller (copied per repo) | on: |
Reusable workflow |
|---|---|---|
call-pull-request-target.yml |
pull_request_target: [opened, review_requested, labeled] |
pull-request-target.yml (dispatcher → review-requested / pull-request-label / dependabot-reviewer) |
call-contributor-issue-comment.yml |
issue_comment: [created] |
contributor-issue-comment.yml |
call-contributor-pr-reply.yml |
pull_request_target: [opened] |
contributor-pr-reply.yml |
call-holiday-message.yml |
pull_request_target: [opened] + issue_comment: [created] |
holiday-message.yml |
call-manage-issue-header.yml |
issues: [opened, reopened, labeled, unlabeled] |
manage-issue-header.yml |
call-update-pr-spreadsheet.yml |
pull_request_target: [assigned, unassigned, opened, closed, reopened, edited, review_requested, review_request_removed] |
update-pr-spreadsheet.yml |
community-contribution-labeling.yml |
issues: [assigned, unassigned] |
community-contribution-label.yml |
unassign-inactive.yaml |
schedule + workflow_dispatch |
unassign-inactive-issues.yaml |
| (no caller yet) | pull_request_review_comment |
resolve_bot_pr_threads.yml |
Problems:
- Adding/removing/toggling an automation is a change in every consumer repo.
- The
types:lists drift and are easy to get wrong: a defaultpull_request_targettrigger fires only onopened, reopened, synchronizeand silently misseslabeled,review_requested,assigned, etc.
Hard constraint — 4-level reusable-workflow nesting cap. The existing chain repo caller → pull-request-target.yml → review-requested.yml → is-contributor.yml is already at the GitHub maximum of 4. A new automation.yml therefore cannot sit on top of the current dispatcher (that would be 5). It must flatten the dispatch — call leaf workflows directly, absorbing pull-request-target.yml's action-based routing — giving repo caller → automation.yml → leaf → is-contributor (4).
The Change
Introduce one reusable automation.yml as the single integration point, generated from a declarative registry that is the source of truth for which events each automation listens to.
Components:
-
automation.yml(reusable,workflow_call) — one job per automation, each guarded by anif:on event/action, eachuses:-ing a leaf workflow. Absorbs the routing currently inpull-request-target.yml(review-requested / pull-request-label / dependabot-reviewer become direct jobs). Enabling/disabling an automation org-wide is a one-line edit here. -
Registry (single source of truth) — one entry per automation declaring: leaf workflow file, trigger events +
types:, the dispatchif:condition, the secrets it needs, and anenabledflag. -
Generator script — reads the registry and writes both
automation.yml's jobs and the caller template'son:block, between generated markers. -
Caller template consumers copy — the generated exhaustive
on:block (the union of every enabled automation's events/types — the part a consumer can't reliably hand-maintain) plus a single jobuses:-ingautomation.yml.
Secrets: explicit, not inherited. automation.yml declares every secret in workflow_call.secrets with required: set (bot-app credentials required; Slack/spreadsheet/GCP optional) and forwards each explicitly to the leaf that needs it. The caller passes secrets explicitly. This keeps it validated and explicit about which secrets each automation depends on, rather than silently forwarding everything with secrets: inherit.
Migration is a hard cutover: once automation.yml and the template exist, the obsolete call-*.yml and pull-request-target.yml are deleted from .github, and each consumer repo swaps its callers for the single copied template in one go.
Out of Scope
- Changing the internal logic of leaf workflows (
review-requested.yml,is-contributor.yml,contributor-issue-comment.yml, etc.) — they are called as-is. pr-statistics.yml— org-internal scheduled workflow, not part of the per-repo entry point.- Changing automation behavior (what each script does); this is a routing/packaging consolidation only.
Acceptance Criteria
Central workflow
- A reusable
automation.yml(workflow_call) exists in.github, with one job per current automation, each guarded by anif:on event/action and calling its leaf workflow directly. - The
action-based routing currently inpull-request-target.yml(review-requested / pull-request-label / dependabot-reviewer) is folded intoautomation.ymlas direct jobs. - No call chain exceeds GitHub's 4-level reusable-workflow limit (
caller → automation.yml → leaf → is-contributor). -
automation.ymldeclares every secret inworkflow_call.secretswith correctrequired:flags (bot-app credentials required; Slack/spreadsheet/GCP optional) and forwards each explicitly to the leaf that uses it — nosecrets: inherit. - Seasonal/optional automations (e.g. holiday-message) are enabled/disabled centrally via the registry, with no per-repo toggle.
- Obsolete
call-*.ymlandpull-request-target.ymlare removed from.github; leaf workflows remain.
Registry & generation
- A declarative registry is the single source of truth, with one entry per automation listing: leaf workflow file, trigger events +
types:, dispatchif:condition, required/optional secrets, and anenabledflag. - A generator produces
automation.yml's jobs and the caller template'son:block from the registry. - The caller template's
on:block is the exhaustive union of every enabled automation's events andtypes:. - A pre-commit hook (run via
prek) regenerates and fails on drift between the registry and the generated files. - A CI workflow runs
prekagainst the repo (it has.pre-commit-config.yamlbut no CI lint check today), matching other learningequality repos.
Migration & cutover
- Org-wide search for
learningequality/.github/.github/workflows/...references is re-run at execution time to confirm the consumer set below is complete. - Org secret visibility is verified to cover every consumer repo (required for explicit secret resolution).
- A migration PR is prepared for each consumer repo, removing all existing callers and adding the single copied template:
-
kolibri -
studio -
ricecooker -
kolibri-design-system -
le-utils -
kolibri-installer-android -
kolibri-app -
kolibri-installer-debian -
kolibri-image-pi -
kolibri-data-portal
-
- All migration PRs are open and ready before any are merged (minimal-downtime cutover).
- After cutover, each event type triggers the same automation it did before (functional parity per automation).
Documentation
- Docs describe the single entry point, how to onboard a new repo (copy the template), and how to add/toggle an automation (edit the registry, regenerate).
Testing
- Run
prekand confirm the generation hook regenerates cleanly, then edit the registry without regenerating and confirm the hook fails on drift; confirm the new CI workflow runsprekand reports the same. - Exercise the migrated caller in one consumer first (e.g.
studio): open/label/assign a PR, comment on an issue, and confirm each automation fires exactly as it did pre-migration (review request, label handling, dependabot reviewer, spreadsheet update, issue header, community label, holiday when enabled). - Confirm the 4-level nesting holds —
automation.yml's jobs that reachis-contributor.ymlrun without GitHub's "maximum workflow nesting" error. - Confirm explicit secrets resolve: a repo missing optional secrets (Slack/spreadsheet/GCP) still runs the automations that don't need them, and required bot-app secrets fail loudly if absent.
References
- Reusable workflows & the 4-level nesting limit: https://docs.github.com/en/actions/sharing-automations/reusing-workflows
- Passing secrets to reusable workflows (
required, explicit forwarding): https://docs.github.com/en/actions/sharing-automations/reusing-workflows#passing-inputs-and-secrets-to-a-reusable-workflow prek(pre-commit runner): https://github.com/j178/prek- Existing dispatcher being flattened:
.github/workflows/pull-request-target.yml - Consumer inventory captured from org code search (this repo, 2026-06-15)
AI usage
Drafted with Claude Code from a verbal description of the desired architecture. The author directed the design decisions (single entry point, declarative registry + generator, explicit secrets over secrets: inherit, hard cutover, pre-commit + CI enforcement). Claude inspected the current workflows, identified the 4-level nesting constraint, ran the org-wide consumer search, and assembled the issue section by section under review. All claims (workflow inventory, consumer list, nesting limit) were verified against the repo and GitHub before inclusion.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.