Skip to content

Latest commit

 

History

44 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 Ask DeepWiki 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.

📚 Ask this codebase (DeepWiki)

Ask DeepWiki

DeepWiki maintains an AI-generated wiki over this repository — architecture pages, diagrams, and a Q&A box grounded in the actual code. Every public Lentago Labs repo is indexed (deepwiki.com/lentago); it is the fastest way to orient before reading source. It is AI-generated: trust it to orient you, verify against the code before you act on it.

Good first questions:

  • How does the claude-responder.yml workflow route between opus, sonnet, and haiku models, and who can trigger it?
  • What does docs-check.yml do differently from a simple link-checker, and why must callers omit the paths: filter?
  • Why is the Claude PR review in claude-review.yml advisory/non-blocking instead of a required status check?

🧭 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

This is a lab — the systems are real, the stakes are not. Pick a vector:

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 team learning lab — real systems, non-critical stakes, modern operations patterns demonstrated in the open. Start at the org profile, and read this repo on DeepWiki.

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