Skip to main content

Repository Automations

This document is a functional overview of the always-on GitHub automations that run on the HAI-Co² repository: what fires, when, what it does, and where each one is configured. It follows the lifecycle of a change, from filing an issue to deploying a release. These are the automations that are live today. The separate, label-gated AI-assisted pathway (an AI agent, Coco, run by gh-aw that plans a labelled issue, warns about higher-risk traits, and implements it after a human comments /approve-plan) is documented in ai-pathway.md; this document covers the rest. For the label vocabulary these automations depend on, see labels-reference.md. For the day-to-day operational view (what a maintainer watches and tunes), see maintainers-handbook.md. How the live automations chain along the lifecycle of a change:

At a glance


1. Filing an issue: issue forms

Trigger: a user clicks New issue. The issue chooser offers four structured forms (Bug, Feature, Docs, Refactor) plus contact links that route usage questions and security reports elsewhere. Blank issues are disabled, so every issue starts from a form. Each form auto-applies its type:* label and needs-triage (the refactor form also pre-applies path:traditional). Note: GitHub reads these forms from the default branch only, so they appear in the UI once merged there. Config: .github/ISSUE_TEMPLATE/.

2. Opening a PR: branch-name validation

Trigger: a PR is opened, edited, reopened, or synchronized against main or develop. The workflow checks the PR’s head branch against <type>/<short-kebab-description> (allowed types: feature, bugfix, fix, hotfix, release, chore, refactor, docs, test, experiment). If it doesn’t match, the check fails with a message listing the convention. main, develop, and bot branches (dependabot/*, renovate/*, github-actions/*) are exempt, so release PRs (develop → main) and dependency-bump PRs pass. Config: branch-name.yml. The same rule is enforced locally (opt-in) by .githooks/pre-push.

3. Opening a PR: area labeler

Trigger: a PR is opened, reopened, synchronized, or marked ready for review. actions/labeler adds area:* labels based on which paths the PR touches (backend/**area:backend, docs/** and *.mdarea:docs, .github/**area:ci, and so on). It is add-only (sync-labels: false), so a label a triager added by hand is never stripped. It runs as pull_request_target, so it also works on fork PRs. Config: labeler.yml (workflow) + .github/labeler.yml (path rules).

4. Every push or PR: CI (lint + test)

Trigger: any push to any branch, and every pull request. Three jobs run in parallel: frontend build + lint, frontend tests, and backend tests (with coverage). A consolidated ci-complete gate then passes only if all three succeeded, which is the single status to mark as a required check in branch protection. Config: ci.yml (the lint/test jobs and the gate).

5. Merges to develop / main: Release Drafter

Trigger: a push (merge) to develop or main, plus pull_request_target open/edit events against those branches. Release Drafter maintains a continuously-updated draft GitHub Release: every merged PR is listed, grouped into categories by its type:* label, and the next SemVer version is computed from those labels. You never write release notes by hand. Two channels run independently so they don’t pollute each other: Three config blocks do the work:
  • categories: maps each type:* label to a notes section (type:enhancement → ”✨ Features”, type:bug → ”🐞 Bug fixes”, …). This mapping is mirrored in the table in labels-reference.md §2.
  • version-resolver: chooses the bump: a breaking label → major, type:enhancement/type:refactor → minor, everything else → patch.
  • autolabeler: infers the type:* label from a Conventional-Commits PR title (feat:type:enhancement, fix:/hotfix:type:bug, …), so even an unlabelled PR is categorised correctly. This is why the PR template asks for a Conventional-Commits title.
Release Drafter only writes the draft; it never deploys. Publishing happens in step 6. The workflows run as pull_request_target so they read the workflow/config from the base branch, required by Release Drafter, which expects its config file to live on the default branch. Config: the two release-drafter-*.yml configs and their workflows. Operational detail (keeping the two autolabeler blocks in sync, watching the draft) is in maintainers-handbook.md.

6. Pushing a tag: build, push, deploy

Trigger: pushing a SemVer tag, dev-vX.Y.Z or vX.Y.Z, after CI is green. The same ci.yml pipeline resolves the tag to a channel, then builds and ships:
  1. resolve-release classifies the tag: dev-vX.Y.Z → the dev GitHub Environment; vX.Y.Z → the prod GitHub Environment.
  2. build-and-push builds the backend and frontend images and pushes them to GHCR, tagged with both the version and the channel tag (:dev or :latest).
  3. deploy connects to the target server over VPN + SSH, generates .env files from the channel’s .env.example (filling values from GitHub Environment secrets), ships the compose tree, and runs the deploy script. A required reviewer on the prod environment can gate production behind a human.
  4. release-summary writes the published image refs and deploy result to the run summary.
So the full release chain is: merge PRs (Release Drafter accumulates notes) → push the tag → ci.yml builds, pushes, and deploys → the draft becomes the published release notes. Config: ci.yml. The branching/release procedure is in CONTRIBUTING.md; the two-branch rationale is ADR-0001.

7. Weekly: stale bot

Trigger: a weekly schedule (Monday 06:00 UTC), or manual dispatch. Deliberately conservative: an issue/PR with no activity for 180 days is labelled status:stale and warned, then closed after a further 21 days unless someone comments. It processes at most 30 items per run, and exempts blocked, help wanted, good first issue, high-priority, and path:ai-automation items. This avoids the well-known anti-pattern of an aggressive stale bot driving contributors away. Config: stale.yml.

8. Weekly: Dependabot

Trigger: a weekly schedule, per ecosystem. Dependabot opens dependency-update PRs across seven streams: github-actions, pip (backend), npm (frontend), and docker for backend, frontend, deployment/dev, and deployment/prod. Minor and patch updates are grouped into single PRs to cut noise, and each PR is labelled type:chore plus the relevant area:*, so it flows through the labeler/Release Drafter machinery like any other change. Config: dependabot.yml.

How labels tie it all together

A single type:* and area:* pair drives most of the chain:
  • Issue forms apply type:* at creation.
  • The labeler applies area:* on the PR from changed paths.
  • Release Drafter reads type:* to place the PR in the right notes section and to compute the version bump, falling back to the PR title via autolabeler.
  • The stale bot reads status/priority labels to decide what to leave alone.
Because of this, the label taxonomy in .github/labels.yml is the connective tissue, and the release-drafter-*.yml categories and labeler rules must stay in sync with it. Apply the taxonomy to a repo with .github/scripts/init-labels.sh.

The AI-assisted pathway

A second, opt-in pathway dispatches labelled issues to a provider-agnostic AI agent (run by gh-aw on the repo’s own API key), gated by a path:ai-automation[-<engine>] label. The engine is chosen by label: the default path:ai-automation (Copilot, needs a seat) or explicit path:ai-automation-copilot; path:ai-automation-claude and path:ai-automation-codex are seat-free. Assigning the HAI-Coco bot account to an issue automatically adds path:ai-automation (via coco-assign-label.yml). Adding the label opens a draft PR with the plan as its first comment; /coco <change> before /approve-plan revises the plan (no code written); /approve-plan implements the plan; CI runs; /promote dev deploys to dev; /coco X after approval iterates on the code; marking the PR ready for review runs Coco’s automated review; a human approves and merges. The workflows live in .github/workflows/ as ai-{plan,implement,iterate,review,review-ondemand}-{claude,codex,copilot}.md (gh-aw sources sharing prompt bodies from .github/aw/shared/, compiled to *.lock.yml), plus the classic coco-plan-comment.yml, coco-approve-ack.yml, coco-assign-label.yml, coco-stage-labels.yml, promote-dev.yml, notify-dev-ready.yml, and notify-approved.yml. A stage:* label (managed by coco-stage-labels.yml) tracks each AI-pathway issue and PR through the lifecycle, so the Issues and Pull-requests lists show where every item is at a glance. The full how-it-works + setup is in ai-pathway.md; the rationale for having two pathways is ADR-0002.

Where to go deeper