Skip to content

ghagen

Generate GitHub Actions workflows from typed Python or TypeScript code.
pip install ghagen

Define your workflow in code:

from ghagen import App, Job, On, PRTrigger, PushTrigger, Step, Workflow
workflow = Workflow(
name="CI",
on=On(
push=PushTrigger(branches=["main"]),
pull_request=PRTrigger(branches=["main"]),
),
jobs={
"test": Job(
runs_on="ubuntu-latest",
steps=[
Step(uses="actions/checkout@v4"),
Step(name="Run tests", run="python -m pytest"),
],
),
},
)
app = App()
app.add_workflow(workflow, "ci.yml")
app.synth()

ghagen generates clean, readable YAML:

name: CI
on:
pull_request:
branches:
- main
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: python -m pytest
  • Typed models for workflows, jobs, steps, triggers, and permissions
  • IDE autocomplete and type checking for every field
  • YAML comment support — block, end-of-line, and field-level comments
  • DRY helpers — step factories (checkout(), setupPython(), etc.) and expression builders
  • Escape hatches — graduated ways to inject arbitrary YAML when the typed models don’t cover what you need
  • CLIghagen synth to generate, ghagen check-synced to verify freshness in CI, ghagen deps pin to lock actions to commit SHAs

GitHub Actions are defined as YAML, which is simple and readable for small projects. As projects grow, however, workflows accumulate shared concerns and repeated logic — the same setup steps, the same version strings, the same conditional blocks copied across files. YAML has no real answer for code reuse: you end up changing the same value in multiple places, and configuration drift between workflows that should behave identically becomes a constant source of bugs.

GitHub has tried to address this with composite actions and YAML anchors, but the fundamental issue remains: YAML is a data format, not a programming language. Rather than bolting programming-language features onto YAML, ghagen takes the approach AWS CDK proved out for CloudFormation — use a real programming language to define your configuration, and generate the YAML from it.

ghagen also tackles a second problem: GitHub Actions dependencies (uses: "setup-node@v2") have no lockfile, so every workflow run can pull a different version. This makes builds non-hermetic and opens the door to supply-chain attacks via package takeovers. Tools like Ratchet address this; ghagen does too, with a lockfile you update explicitly via ghagen deps pin.