
❌ 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 default pull_request_target trigger fires only on opened, reopened, synchronize and silently misses labeled, 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 an if: on event/action, each uses:-ing a leaf workflow. Absorbs the routing currently in pull-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 dispatch if: condition, the secrets it needs, and an enabled flag.
-
Generator script — reads the registry and writes both automation.yml's jobs and the caller template's on: 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 job uses:-ing automation.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
Registry & generation
Migration & cutover
Documentation
Testing
- Run
prek and confirm the generation hook regenerates cleanly, then edit the registry without regenerating and confirm the hook fails on drift; confirm the new CI workflow runs prek and 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 reach is-contributor.yml run 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
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.
❌ 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-*.ymlcaller workflows that every consuming repo must copy with a singleautomation.ymlreusable 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 exhaustiveon:block and a singleuses: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 ownon:trigger and exhaustivetypes:list, eachuses:-ing a reusable workflow:on:call-pull-request-target.ymlpull_request_target: [opened, review_requested, labeled]pull-request-target.yml(dispatcher → review-requested / pull-request-label / dependabot-reviewer)call-contributor-issue-comment.ymlissue_comment: [created]contributor-issue-comment.ymlcall-contributor-pr-reply.ymlpull_request_target: [opened]contributor-pr-reply.ymlcall-holiday-message.ymlpull_request_target: [opened]+issue_comment: [created]holiday-message.ymlcall-manage-issue-header.ymlissues: [opened, reopened, labeled, unlabeled]manage-issue-header.ymlcall-update-pr-spreadsheet.ymlpull_request_target: [assigned, unassigned, opened, closed, reopened, edited, review_requested, review_request_removed]update-pr-spreadsheet.ymlcommunity-contribution-labeling.ymlissues: [assigned, unassigned]community-contribution-label.ymlunassign-inactive.yamlschedule+workflow_dispatchunassign-inactive-issues.yamlpull_request_review_commentresolve_bot_pr_threads.ymlProblems:
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.ymlis already at the GitHub maximum of 4. A newautomation.ymltherefore cannot sit on top of the current dispatcher (that would be 5). It must flatten the dispatch — call leaf workflows directly, absorbingpull-request-target.yml'saction-based routing — givingrepo caller → automation.yml → leaf → is-contributor(4).The Change
Introduce one reusable
automation.ymlas 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.ymldeclares every secret inworkflow_call.secretswithrequired: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 withsecrets: inherit.Migration is a hard cutover: once
automation.ymland the template exist, the obsoletecall-*.ymlandpull-request-target.ymlare deleted from.github, and each consumer repo swaps its callers for the single copied template in one go.Out of Scope
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.Acceptance Criteria
Central workflow
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.action-based routing currently inpull-request-target.yml(review-requested / pull-request-label / dependabot-reviewer) is folded intoautomation.ymlas direct jobs.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.call-*.ymlandpull-request-target.ymlare removed from.github; leaf workflows remain.Registry & generation
types:, dispatchif:condition, required/optional secrets, and anenabledflag.automation.yml's jobs and the caller template'son:block from the registry.on:block is the exhaustive union of every enabled automation's events andtypes:.prek) regenerates and fails on drift between the registry and the generated files.prekagainst the repo (it has.pre-commit-config.yamlbut no CI lint check today), matching other learningequality repos.Migration & cutover
learningequality/.github/.github/workflows/...references is re-run at execution time to confirm the consumer set below is complete.kolibristudioricecookerkolibri-design-systemle-utilskolibri-installer-androidkolibri-appkolibri-installer-debiankolibri-image-pikolibri-data-portalDocumentation
Testing
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.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).automation.yml's jobs that reachis-contributor.ymlrun without GitHub's "maximum workflow nesting" error.References
required, explicit forwarding): https://docs.github.com/en/actions/sharing-automations/reusing-workflows#passing-inputs-and-secrets-to-a-reusable-workflowprek(pre-commit runner): https://github.com/j178/prek.github/workflows/pull-request-target.ymlAI 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.