Skip to content

Consolidate shared workflows into a single generated automation.yml entry point #86

@rtibbles

Description

@rtibbles

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:

  1. 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.

  2. 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.

  3. Generator script — reads the registry and writes both automation.yml's jobs and the caller template's on: block, between generated markers.

  4. 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

  • A reusable automation.yml (workflow_call) exists in .github, with one job per current automation, each guarded by an if: on event/action and calling its leaf workflow directly.
  • The action-based routing currently in pull-request-target.yml (review-requested / pull-request-label / dependabot-reviewer) is folded into automation.yml as direct jobs.
  • No call chain exceeds GitHub's 4-level reusable-workflow limit (caller → automation.yml → leaf → is-contributor).
  • automation.yml declares every secret in workflow_call.secrets with correct required: flags (bot-app credentials required; Slack/spreadsheet/GCP optional) and forwards each explicitly to the leaf that uses it — no secrets: inherit.
  • Seasonal/optional automations (e.g. holiday-message) are enabled/disabled centrally via the registry, with no per-repo toggle.
  • Obsolete call-*.yml and pull-request-target.yml are 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:, dispatch if: condition, required/optional secrets, and an enabled flag.
  • A generator produces automation.yml's jobs and the caller template's on: block from the registry.
  • The caller template's on: block is the exhaustive union of every enabled automation's events and types:.
  • A pre-commit hook (run via prek) regenerates and fails on drift between the registry and the generated files.
  • A CI workflow runs prek against the repo (it has .pre-commit-config.yaml but 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 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions