Read this file before making any changes. It provides essential context for AI agents working in this repository.
A public monorepo of reusable GitHub Actions workflows and composite actions maintained by Lerian.
Every change here can affect all repositories in the organization that consume these workflows — treat it accordingly.
Full documentation: README.md | Contributing: CONTRIBUTING.md | Security: SECURITY.md
Claude Code CLI commands (load at session start for full context):
/gha— complete reference (workflows + composites + refactoring protocol)/workflow— reusable workflow rules only/composite— composite action rules only/refactor— refactoring protocol for modifying existing workflows or composites
| Type | Location | Purpose |
|---|---|---|
| Reusable workflow | .github/workflows/*.yml |
Callable via workflow_call by other repos |
| Composite action | src/<capability>/<name>/ |
Reusable step logic, called from reusable workflows |
Composite actions are grouped by capability: src/setup/, src/build/, src/test/, src/deploy/, src/config/.
Each composite must have an action.yml and a README.md.
Full rules:
- Composite actions →
.cursor/rules/composite-actions.mdcor/composite - Reusable workflows →
.cursor/rules/reusable-workflows.mdcor/workflow - Modifying existing files →
.cursor/rules/refactoring.mdcor/refactor
In reusable workflows (workflow_call), uses: ./path resolves to the caller's workspace, not this repository. This means ./src/... only works when the caller IS this repo (i.e., self-* workflows).
-
Workflows called by external repos — use an external ref pinned to a release tag:
uses: LerianStudio/github-actions-shared-workflows/src/notify/discord-release@v1.2.3 # ✅ pinned uses: LerianStudio/github-actions-shared-workflows/src/notify/discord-release@develop # ⚠️ testing only uses: ./src/notify/discord-release # ❌ resolves to caller's workspace
-
self-*workflows (internal only) — use a local path:uses: ./.github/workflows/labels-sync.yml # ✅ caller is this repo
Every reusable workflow and composite action that performs conditional work (e.g. change detection, feature-flag checks) must expose boolean outputs so callers can skip downstream jobs when there is nothing to do.
# Reusable workflow example
outputs:
has_builds:
description: 'Whether any components were detected for building (true/false)'
value: ${{ jobs.prepare.outputs.has_builds }}
# Composite action example
outputs:
has_changes:
description: 'Whether any changes were detected (true/false)'
value: ${{ steps.detect.outputs.has_changes }}Callers use these outputs to gate dependent jobs:
jobs:
build:
uses: LerianStudio/github-actions-shared-workflows/.github/workflows/build.yml@v1.2.3
deploy:
needs: build
if: needs.build.outputs.has_builds == 'true'
uses: LerianStudio/github-actions-shared-workflows/.github/workflows/deploy.yml@v1.2.3Every reusable workflow must include a dry_run input (boolean, default: false).
dry_run: true must be verbose (print all resolved values, use tool debug flags). dry_run: false must be silent (no extra echo, no debug flags).
- PRs always target
develop— nevermaindirectly - Follow Conventional Commits:
type(scope): subject - See
CONTRIBUTING.mdfor the full type/version-bump table and scope list
| File | Reason |
|---|---|
CHANGELOG.md |
Auto-generated by semantic-release on every release |
.releaserc.yml |
Drives all release automation — changes need DevOps review |
.github/CODEOWNERS |
Controls review routing — discuss with the team first |
File extension: always use .yml, never .yaml — for workflows, composites, and any YAML config files.
Always use the structured templates in .github/ISSUE_TEMPLATE/. Choose the right one based on the nature of the request:
| Template | File | When to use |
|---|---|---|
| Bug Report | bug_report.yml |
Something in a workflow is broken or behaving unexpectedly |
| Feature Request | feature_request.yml |
New workflow, new input/output, or enhancement to an existing one |
| Documentation | documentation.yml |
Missing, incorrect, or unclear documentation |
Read the chosen template file before drafting the issue to ensure all required fields are filled in correctly. Never open a blank issue — blank_issues_enabled: false is enforced by config.yml.
For security vulnerabilities, never open a public issue — follow the process in SECURITY.md.
Always read .github/pull_request_template.md before drafting a PR description. Fill in every section — do not leave placeholder comments in place.
Key fields to always complete:
- Description — what changed and why, specific to the affected workflow(s)
- Type of Change — mark all that apply
- Breaking Changes — if none, leave
None.; if yes, describe what breaks and how to migrate - Testing — check off what was actually validated; add the caller repo / run link if a real run was triggered
PR titles must follow Conventional Commits: type(scope): subject (lowercase, imperative, max 72 chars).
PRs always target develop — never main directly.
Before modifying any existing file, follow the refactoring protocol in .cursor/rules/refactoring.mdc (or run /refactor in Claude CLI):
- Summarize the current state of the file (inputs, outputs, jobs/steps, dry_run presence)
- Produce a numbered plan with impact classification for each change
- Flag breaking changes explicitly and provide a migration guide
- Propose test examples using
dry_run: trueon@develop - Ask the user to confirm each step before applying — never apply changes without confirmation
- Apply one step at a time and update documentation after all confirmed steps
What must never change without explicit confirmation: input/output names, default values, step ordering that affects downstream outputs, required secrets.
- Third-party actions (outside
LerianStudioorg) must be pinned by commit SHA, not by tag — tags are mutable and can be force-pushed by upstream maintainers. Add a# vX.Y.Zcomment for readability. Dependabot keeps SHA pins updated automatically.uses: actions/checkout@abc123def456 # v6 # ✅ third-party pinned by SHA uses: crazy-max/ghaction-import-gpg@2dc316d # v7 # ✅ third-party pinned by SHA uses: LerianStudio/github-actions-shared-workflows/src/notify/discord-release@v1.2.3 # ✅ org-owned pinned by tag
LerianStudio/*actions use release tags (@v1.2.3) or branches (@developfor testing) — no SHA pinning needed for org-owned actions- Never use
@mainor@masterfor third-party actions - Never hardcode tokens, org names, or internal URLs — use
inputsorsecrets - Never print secrets via
echo,runoutput, or step summaries - Vulnerability reports go through private channels only — see
SECURITY.md
- NEVER use
actions/checkoutwithref: ${{ github.event.pull_request.head.ref }}orref: ${{ github.event.pull_request.head.sha }}inpull_request_targetworkflows - If
pull_request_targetis needed (e.g., labeling, commenting), it MUST NOT run any code from the fork (no build, no test, no script execution from the PR branch) - Prefer
pull_requesttrigger overpull_request_targetunless write permissions to the base repo are explicitly required - NEVER use
secrets: inheritin workflows triggered bypull_request_target— it exposes all repository secrets to fork code
Never use these directly in run: steps — always pass through an env var:
# ❌ These are injectable — NEVER interpolate directly in run:
${{ github.event.pull_request.title }} ${{ github.event.pull_request.body }}
${{ github.event.issue.title }} ${{ github.event.issue.body }}
${{ github.event.comment.body }} ${{ github.event.head_commit.message }}
${{ github.event.head_commit.author.name }}
${{ github.event.commits[*].message }} ${{ github.event.discussion.title }}
${{ github.event.discussion.body }} ${{ github.event.review.body }}
${{ github.head_ref }}
${{ github.event.pages[*].page_name }}
# ✅ Safe — map to env var first
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "$PR_TITLE"- Workflows triggered by
workflow_runMUST NOT trust artifacts from the triggering workflow blindly - Validate/sanitize any data extracted from artifacts before use in shell commands or API calls
- Never extract and execute scripts from artifacts without verification
- ALWAYS declare explicit
permissions:block at workflow level; default tocontents: read - Only escalate permissions per-job when needed
- NEVER use
permissions: write-allor leave permissions undeclared (defaults to broad access in public repos) - For comment/label-only workflows:
permissions: { pull-requests: write }— nothing else
- NEVER pass secrets to steps that run fork code
pull_requestfrom a fork does NOT have access to secrets by default — do not circumvent this- If a workflow needs secrets + fork code, split:
pull_request(no secrets, runs fork code) +workflow_run(has secrets, trusted code only)
- Validate branch names and label names before using in shell commands
- Branch names can contain shell metacharacters — always quote variables and map through
env:
- NEVER use self-hosted runners for
pull_requestorpull_request_targetfrom public repos — a fork can execute arbitrary code on the runner - Self-hosted runners are only safe for
push,workflow_dispatch,schedule, and other non-fork triggers