Skip to content

About

Reusable GitHub Actions workflows shared across the Lentago Labs fleet: an interactive @claude responder (routes to opus/sonnet/haiku by label), automated PR review, CI patterns, and fleet-wide policy enforcement. The canonical source for cross-repo CI conventions.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

53 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

shared-workflows — Reusable CI · the fleet's canonical workflows

main License OpenSSF Scorecard

GitHub Actions Claude YAML

shared-workflows

This is the Lentago Labs fleet's reusable-CI hub: one repository of workflow_call definitions — an agentic @claude responder and reviewer, a docs-link checker, ShellCheck, and a Terraform lint suite — that every Lentago Labs repo calls by reference rather than copying into its own .github/. Callers pin to an immutable semver tag (@v1.0.0); upgrading means a Dependabot-opened bump PR in the caller repo, not silent propagation. If you manage CI for more than a couple of repos, this is the "define the pipeline once, everyone inherits it" pattern working end to end — the enterprise rhyme is a central pipeline library.

Authorship: The workflows and documentation in this repo are co-written with Claude (Anthropic). I direct the work and review the output; Claude writes the YAML. I'm an infrastructure operator, not a software engineer — please don't read this repo as a portfolio of coding ability.

Architecture decisions: docs/adr/ records the reasoning behind this repo's structural choices — canonical policy placement, the @main→semver-tag migration, the advisory review gate, backwards-compatible contracts.

🧭 What this repo demonstrates

Every row is a pattern you can lift into your own CI, with a link to where it actually runs here.

Pattern How it shows up here
Reusable workflows, called by reference not copy-paste — fix or extend the pipeline in one place, consumers upgrade via a Dependabot-opened bump PR Each definition is on: workflow_call; callers write uses: lentago/shared-workflows/.github/workflows/<name>.yml@v1.0.0 (claude-responder.yml)
Agentic PR responder with label-gated model routing — let an LLM act inside CI with human-controlled cost/capability tiers set by a label, not a per-repo hardcode claude-responder.yml reads model:opus / model:sonnet / model:haiku off the issue or PR to pick --model, falling back to default_model
Advisory (non-blocking) automated review — decouple "useful signal" automation from "merge gate" automation so a flaky AI call never stalls delivery claude-review.yml runs the review step continue-on-error, always exits 0, and posts a soft-fail comment instead of reddening the check
A required check must be unconditional — a required status check whose workflow never triggers is held "Expected" forever and deadlocks every non-matching PR docs-check.yml documents "no paths: filter"; docs-check / docs-check is the one required check on this repo's main today
Dogfood a reusable workflow on its own source — catch a broken reusable in its own PR, not only in a downstream caller docs-check-self.yml calls uses: ./.github/workflows/docs-check.yml (local path, not @main) so a PR is checked by the version inside that PR
Ref-resolution correctness for cross-repo checkouts — a reusable that checks out its own repo must use the caller's pinned ref, not the caller's docs-check.yml resolves github.job_workflow_ref (not workflow_ref); a self-referential test masked the bug for external callers until #30
One canonical policy source with declared, manually-audited mirrors — govern org-wide conventions from one file while being explicit about where the text is duplicated and why CLAUDE.md's "Fleet PR-workflow (canonical source)" section names its two mirrors and states the keep-in-sync obligation (manual discipline, not automation)
Backwards-compatible workflow_call contracts — interface discipline for shared automation: a new required input breaks every caller silently CLAUDE.md conventions: add inputs as optional with sensible defaults, never required

Workflows

These definitions are the fleet's CI-as-code surface. Each block below is the exact caller YAML an operator copies into a consuming repo's .github/workflows/ — the caller passes a thin with: block and the reusable workflow handles auth, context, output format, and the heavy lifting.

claude-responder.yml

Interactive @claude responder — the team-facing on-ramp to the agent fleet. It fires when the literal string @claude appears in a comment, review, or issue/PR body or title, and routes to opus/sonnet/haiku based on model:opus / model:sonnet / model:haiku labels on the issue or PR (re-apply the label to re-trigger with the new model). With no model:* label it uses the caller's default_model (default sonnet). For ops readers: this is a cost/capability dial an issue-triager controls with a label, so the expensive model is opt-in per thread rather than baked into every repo.

name: Claude Code

on:
  issue_comment:
    types: [created]
  pull_request:
    types: [opened, synchronize, labeled]
  pull_request_review_comment:
    types: [created]
  pull_request_review:
    types: [submitted]
  issues:
    types: [opened, edited, labeled, assigned]

jobs:
  claude:
    uses: lentago/shared-workflows/.github/workflows/claude-responder.yml@v1.0.0
    secrets: inherit
    with:
      allowed_tools: '"Bash(git add:*)" "Bash(git commit:*)" "Read" "Edit" "Write"'
      # default_model: opus      # optional, default "sonnet"
      # max_turns: 25             # optional
      # extra_args: |             # optional, e.g. --verbose
      #   --verbose
      #   --output-format stream-json

claude-review.yml

Automated PR review pinned to Haiku. Caller passes a focus block describing the repo and what to look for; the reusable workflow wraps it with the common PR-context header, rules, and output-format scaffolding.

The review is advisory and non-blocking: the review step runs continue-on-error and the job always exits 0, so a transient API failure or turn-budget exhaustion never reds the check or blocks a merge — if the review can't complete, a neutral soft-fail comment is posted instead. This is the deliberate "useful signal, not a gate" split: it is not a required check, and you should not treat it as one. Optional inputs: model (default haiku), max_turns (default 40, set high enough that a normal review finishes before the cap), allowed_bots (default "*", so the fleet's own agent-opened PRs get reviewed too).

name: Claude Code Review

on:
  pull_request:
    types: [opened, synchronize, ready_for_review, reopened]
    paths-ignore:
      - "*.md"
      - "docs/**"
      - "LICENSE"
      - ".gitignore"

jobs:
  claude-review:
    uses: lentago/shared-workflows/.github/workflows/claude-review.yml@v1.0.0
    secrets: inherit
    with:
      review_prompt: |
        This repository is X. Focus your review on:

        1. **Foo** — ...
        2. **Bar** — ...

docs-check.yml

Resolves relative markdown links across a repo's git-tracked markdown and fails on genuinely broken ones — the fleet's most common change class ships documentation, and renames/removals silently break relative links. It is more than a generic link-checker: it understands the fleet's two known false-positive classes (below) and resolves them by rule instead of flagging them.

Trigger it unconditionally — no paths: filter. This workflow is built to serve as a required status check, and a required check whose workflow never triggers is held "Expected" forever and deadlocks every non-matching PR (the hard rule in fleet-ops/required-checks.json). Trigger on pull_request across the board; the checker is cheap and skips fast when no markdown changed.

The checker skips two link classes by rule — the two that accounted for 237 of 240 raw failures in the fleet-wide scan (lentago/.github#57):

  • Site-absolute router routes (/library/, /guides/stormwater/) — resolved by an Astro/Starlight site router at build time, not the filesystem.
  • Links that escape the repo root (../../issues/5) — GitHub's repo-relative navigation convention, which resolves on github.com and can never point at a tracked file anyway.

Anything left is handled by the ignore input (newline-separated globs matched against the source-file path or the link target) or a checked-in .docs-check-ignore file at the repo root (one glob per line, # comments).

name: docs-check

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]
    # No paths: filter — this is a required check and must always report.

jobs:
  docs-check:
    uses: lentago/shared-workflows/.github/workflows/docs-check.yml@v1.0.0
    with:
      # ignore: |                 # optional, per-repo false positives
      #   */api-reference/*
      #   CHANGELOG.md
      # paths: "*.md *.markdown"   # optional, default shown

The link resolver lives at ci/check_docs_links.py — a single, testable source promoted from lentago/.github's ci/validate.py. Run its test suite locally with python3 ci/test_check_docs_links.py. This repo also dogfoods the checker on its own markdown via docs-check-self.yml, which uses the local path ref so a PR is validated by the resolver inside that PR.

shellcheck.yml

ShellCheck for bash scripts. Pass an explicit scripts list, or leave it empty for repo-wide find -name '*.sh' discovery.

name: ShellCheck

on:
  pull_request:
    types: [opened, synchronize, ready_for_review, reopened]

jobs:
  shellcheck:
    uses: lentago/shared-workflows/.github/workflows/shellcheck.yml@v1.0.0
    with:
      scripts: |
        deploy.sh
        scripts/start.sh
      # severity: warning  # optional, default "warning"

tf-lint.yml

Terraform quality gate — runs four checks over a caller-specified directory: terraform fmt -check -recursive (formatting), terraform init -backend=false + terraform validate (config validity — no cloud credentials required, deliberately runnable on fork PRs), tflint (idiomatic Terraform and provider-specific rules), and trivy config (IaC misconfiguration scan; trivy is chosen for consistency with site-deploy.yml's container scan, one tool across two surfaces).

Each gate is individually toggleable via a boolean input. Trigger it unconditionally — no paths: filter if you intend to register it as a required status check. A required check whose workflow never triggers is held "Expected" forever and deadlocks every non-matching PR (the hard fleet rule).

name: Terraform Lint

on:
  pull_request:
    types: [opened, synchronize, ready_for_review, reopened]
    # No paths: filter if this is a required check — see tf-lint.yml header.

jobs:
  tf-lint:
    uses: lentago/shared-workflows/.github/workflows/tf-lint.yml@v1.2.0
    with:
      working_directory: terraform   # optional, default "terraform"
      # terraform_version: "1.9.8"  # optional, default "latest"
      # tflint_version: "v0.54.0"   # optional, default "latest"
      # enable_fmt: false            # optional — disable if fmt is enforced elsewhere
      # enable_validate: false       # optional
      # enable_tflint: false         # optional
      # enable_trivy: false          # optional

Full input reference:

Input Required Default Description
working_directory no terraform Directory containing Terraform configuration, relative to repo root
terraform_version no latest Terraform version to install (e.g. 1.9.8)
tflint_version no latest tflint version to install (e.g. v0.54.0)
enable_fmt no true Run terraform fmt -check -recursive
enable_validate no true Run terraform init -backend=false + terraform validate
enable_tflint no true Run tflint
enable_trivy no true Run trivy config (IaC misconfiguration scan)

site-deploy.yml

Reusable Astro-site deployment pipeline: builds the static site, packages it into a Docker image, pushes to ECR (:latest and :<sha> tags), scans for vulnerabilities with Trivy, attests the image with actions/attest-build-provenance, then rolls the ECS service and waits for stabilization. Authenticates via OIDC — no long-lived AWS credentials.

SLSA Build L3: building inside a reusable workflow (hosted in a separate repository that callers cannot modify) satisfies SLSA Build L3. Attestations are stored in GitHub's Sigstore-backed store and can be verified offline:

gh attestation verify oci://<registry>/<ecr_repo>@<digest> --owner lentago

Caller permissions required (on the calling workflow's job: block):

permissions:
  id-token: write      # OIDC for AWS auth and attestation signing
  contents: read
  attestations: write  # write to GitHub's attestation store
  packages: read       # required by actions/attest-build-provenance

The caller wires up secrets: inherit so the OIDC token and any other org/repo secrets are forwarded transparently.

Minimum caller example (pin to the release that introduced this workflow):

name: Build & Deploy

on:
  push:
    branches: [main]
  workflow_dispatch: {}

jobs:
  deploy:
    uses: lentago/shared-workflows/.github/workflows/site-deploy.yml@v1.1.0
    secrets: inherit
    permissions:
      id-token: write
      contents: read
      attestations: write
      packages: read
    with:
      ecr_repo: solidago-dev-mysite
      ecs_cluster: solidago-dev-cluster
      ecs_service: solidago-dev-mysite
      role_arn: arn:aws:iam::365184644049:role/solidago-dev-github-actions

Full input reference:

Input Required Default Description
ecr_repo yes — ECR repository name
ecs_cluster yes — ECS cluster name
ecs_service yes — ECS service name
role_arn yes — IAM role ARN to assume via OIDC
aws_region no us-east-1 AWS region
node_version no 20 Node.js version for the Astro build
fetch_depth no 1 Git fetch depth (0 = full history; required for Starlight lastUpdated)
pre_build_command no "" Shell command run after npm ci and before npm run build (e.g. a Python content-sync script)
build_env_vars no "" Newline-separated KEY=VALUE pairs exported as env vars for the build step (e.g. PUBLIC_ASK_ENDPOINT=${{ vars.PUBLIC_ASK_ENDPOINT }})
attest no true Attest the pushed image with actions/attest-build-provenance

Site-specific examples:

# site-icecreamtofightwith-com: Python content sync before build
with:
  ecr_repo: solidago-dev-app
  ecs_cluster: solidago-dev-cluster
  ecs_service: solidago-dev-app
  role_arn: arn:aws:iam::365184644049:role/solidago-dev-github-actions
  pre_build_command: python3 sync_recipes.py

# site-pondviewlane-com: full git history for Starlight lastUpdated + build env var
with:
  ecr_repo: solidago-dev-pondview
  ecs_cluster: solidago-dev-cluster
  ecs_service: solidago-dev-pondview
  role_arn: arn:aws:iam::365184644049:role/solidago-dev-github-actions
  node_version: "24"
  fetch_depth: 0
  build_env_vars: "PUBLIC_ASK_ENDPOINT=${{ vars.PUBLIC_ASK_ENDPOINT }}"

Trivy scan runs informational-only (exit-code: 0) — it reports CRITICAL/HIGH unfixed findings in the job log but never blocks the deploy. This gives visibility without introducing a new breakage vector on an existing fleet of live sites.

Versioning

Callers pin to an immutable semver tag — @v1.0.0, @v1.1.0, etc. (ADR-0005). A full release process — what must be green before tagging, the semver policy for reusable workflows (what counts as a breaking change vs. minor vs. patch), and how callers upgrade — is in RELEASING.md.

@main is not a supported consumption path. It continues to work mechanically but carries no stability guarantee; a merge here may change caller CI instantly and silently.

🛠️ Make a change yourself

These systems are real, and nothing critical rides on them. That makes this a safe place to try a change before you make the same kind of change in your own shop. Pick one:

Invoke the fleet agent from any issue or PR. This is the lowest-friction on-ramp — no code, just a comment. Write @claude in a comment, review, or the body/title of an issue or PR on any repo that wires up the responder, and the claude-responder.yml if: predicate fires the agent. Add a model:opus, model:sonnet, or model:haiku label to route which model answers (the label overrides the caller's default_model; re-apply it to re-trigger). Note on access: nothing in this workflow restricts invocation to org members — the literal-@claude predicate is all that gates it, so who can reach the agent is governed by the caller repo's own event and trigger permissions, not by a check here. Proof this works: the agentic layer is actively hardened — #15 fix(claude-review): make the reviewer advisory and non-blocking and #10 fix(claude-review): default allowed_bots to "*" so fleet bot PRs get reviewed.

Ship a new reusable CI check to the whole fleet. Add or edit a workflow_call definition under .github/workflows/, test it by pointing one caller repo's uses: at @<branch-name> for a single merge, then open a PR here. After it merges to main, cut a release tag — caller repos pick up the change when they bump their uses: ref to the new tag (Dependabot opens those bumps as reviewable PRs where it is enabled). See RELEASING.md. Proof this works (historical, pre-versioning): #28 Add reusable docs-check workflow (relative markdown links), #29 Dogfood docs-check on this repo's own markdown, and #30 docs-check: resolve the tooling ref from job_workflow_ref — the last fixed a cross-repo-caller bug (job_workflow_ref vs workflow_ref) that a self-referential test had masked for every external caller.

Amend the fleet's canonical PR-workflow policy. The CLAUDE.md "Fleet PR-workflow (canonical source)" section is the single source of truth for the fleet's PR conventions. Editing it means propagating the same change to its two documented mirrors — ~/repos/CLAUDE.md and the review_prompt block in claude-review.yml — in the same PR. That sync is a stated manual obligation, not automation, so the discipline is part of the change. Proof this works: #11 docs: neutralize PR voice — drop the ## Origin section and Prompt-Origin trailer and #6 Re-anchor fleet PR-workflow as canonical here + add CI review checks, which folded the rules into claude-review.yml's prompt as machine-checked review criteria.


🌱 Lentago Labs is a pro-bono operations practice for organizations that run on volunteers, donations, and one overworked tech person. Everything here is free to take, and we practice what we publish: our own estate runs this way, in the open. Start at the org profile.

About

Reusable GitHub Actions workflows shared across the Lentago Labs fleet: an interactive @claude responder (routes to opus/sonnet/haiku by label), automated PR review, CI patterns, and fleet-wide policy enforcement. The canonical source for cross-repo CI conventions.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages