How to Author Required Workflows¶
This guide is for the Platform Team responsible for maintaining the cockpit organization's workflow catalog. It covers designing, testing, versioning, and releasing the required workflows that org-level rulesets mandate every product repository to run. For an overview of what required workflows are and how rulesets enforce them, see Required Workflows.
Workflow design principles¶
Required workflows are the platform's mandated CI/CD standards. Every input, output, and behavior is a contract with every consuming repository. Design accordingly.
| Principle | Rationale |
|---|---|
| Minimize required inputs | Fewer required inputs means faster adoption. Use sensible defaults for everything except truly repo-specific values. |
| Type and document every input | Consumers should never have to read the workflow source to understand what to pass. |
| Never break consumers on minor/patch | A non-breaking change means no renamed inputs, no removed steps, and no altered default behavior. |
| Scope permissions to the minimum | Request only the GITHUB_TOKEN permissions each job actually needs. Never use permissions: write-all. |
| One workflow, one responsibility | Each workflow should do one thing well. Compose workflows in the caller, not inside a single monolith. |
Tip
Write a README.md for every workflow. Include the purpose, all inputs with types and defaults, outputs, required secrets, and a minimal caller example. The README is the contract documentation.
When to create a new workflow¶
Not every automation belongs in the catalog. Create a new required workflow when:
- The same pattern appears in three or more teams independently.
- A step is security-critical and must be executed consistently (e.g., artifact signing, vulnerability scanning).
- A compliance requirement mandates a specific process that cannot be left to individual teams.
If only one team needs it, it belongs in that team's repository. If two teams need it, watch the pattern. At three, promote it to the catalog.
Authoring a new workflow¶
File structure¶
Place workflow files in the cockpit organization under .github/workflows/. Follow this structure:
# .github/workflows/build.yml
name: Build
on:
workflow_call:
inputs:
language:
description: "Runtime/language (node, java, dotnet, python, go)"
required: true
type: string
language-version:
description: "Version of the runtime"
required: false
type: string
default: "latest"
artifact-name:
description: "Name for the uploaded artifact"
required: false
type: string
default: ""
secrets:
REGISTRY_TOKEN:
description: "Token for the artifact registry (platform-owned)"
required: false
outputs:
artifact-id:
description: "ID of the uploaded artifact"
value: ${{ jobs.build.outputs.artifact-id }}
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
- uses: actions/checkout@v4
# ... build steps per language
Input design¶
| Practice | Example |
|---|---|
Use required: true only for values the workflow cannot guess |
language is required; language-version defaults to latest |
| Provide defaults for optional inputs | coverage-threshold defaults to 80 |
Use choice descriptions to list valid values |
"Runtime: node, java, dotnet, python, go" |
Prefer string type with validated values over boolean flags |
scan-type: "sast" instead of run-sast: true |
Secret handling¶
- Platform-owned secrets (registry tokens, scan API keys): stored in the cockpit organization and accessed directly by the workflow. Consumers never see or manage these.
- Caller-owned secrets: passed via
secrets: inheritor explicitsecrets:inputs. Used for repo-specific credentials. - Never log secret values. Use
::add-mask::for any derived values that might appear in logs.
Warning
Do not use secrets: inherit inside the required workflow definition itself. That directive is for the caller side. On the producer side, declare each secret explicitly with required: true or required: false.
Permission scoping¶
Declare the minimum permissions block at the workflow level. Common patterns:
# Read-only workflow (test, scan)
permissions:
contents: read
# Workflow that publishes packages
permissions:
contents: read
packages: write
# Workflow that comments on PRs
permissions:
contents: read
pull-requests: write
Error handling¶
- Use
if: failure()steps to produce clear error summaries for consumers. - Set
continue-on-error: false(the default) for critical steps. Only usecontinue-on-error: truefor non-blocking advisory checks. - Write step outputs that indicate pass/fail so callers can branch on results.
Testing workflows before release¶
Every workflow change must be tested before it reaches consumers.
Test infrastructure¶
- Dedicated test repositories in the cockpit organization, one per supported language/stack.
- Each test repo contains a caller workflow that references the workflow under test using a branch ref (
@feature-branch). - A test matrix covers the supported combinations:
# In the workflow repo's own CI
jobs:
test-matrix:
strategy:
matrix:
language: [node, java, python, go]
version: ["18", "21", "3.12", "1.22"]
uses: ./.github/workflows/build.yml
with:
language: ${{ matrix.language }}
language-version: ${{ matrix.version }}
Local testing with act¶
Use act for fast local iteration before pushing:
act -W .github/workflows/build.yml \
--input language=node \
--input language-version=20
Note
act does not support all GitHub Actions features (e.g., secrets: inherit, OIDC tokens). Use it for logic validation, not as a replacement for the full test matrix.
CI pipeline for the workflow repository¶
The workflow repository itself must have a CI pipeline that runs on every pull request:
- Lints workflow YAML with
actionlint - Runs the test matrix against test repositories
- Validates that no
permissionsblock exceeds the documented scope
Versioning and release process¶
Tagging strategy¶
| Tag | Points to | When to create |
|---|---|---|
v1.2.3 |
Exact commit (immutable) | Every release |
v1.2 |
Latest v1.2.x commit |
Re-pointed on each patch release |
v1 |
Latest v1.x.x commit |
Re-pointed on each minor/patch release |
After merging to main, tag and release:
git tag -a v1.3.0 -m "feat: add Go module caching support"
git push origin v1.3.0
# Update floating tags
git tag -f v1.3 v1.3.0
git tag -f v1 v1.3.0
git push origin v1.3 v1 --force
CHANGELOG¶
Maintain a CHANGELOG.md per workflow (or a single file if the repo contains one workflow). Every entry includes the version, date, and a summary of changes with migration notes if applicable.
Breaking changes¶
A breaking change is any modification that can cause a consumer's workflow to fail or behave differently without action on their part: renamed inputs, removed steps, changed defaults, or altered outputs.
Breaking changes require:
- Major version bump (
v1tov2). - Advance notice to all consumers at least two weeks before release.
- Migration guide in the CHANGELOG and README.
- Parallel availability: keep
v1functional whilev2is adopted.
Deprecation process¶
- Announce: mark the workflow or input as deprecated in the README and CHANGELOG.
- Grace period: maintain the deprecated version for at least 90 days.
- Remove: delete the deprecated version only after confirming zero active consumers.
Maintaining existing workflows¶
Dependency updates¶
- Pin all third-party actions to exact versions or SHA refs (
actions/checkout@v4.1.1oractions/checkout@abcdef1). - Use Dependabot or Renovate on the workflow repository itself.
- Review action updates for behavior changes before merging.
Security patching¶
| Severity | Response |
|---|---|
| Critical (active exploit, secret exposure) | Patch and release immediately. Re-point floating tags within hours. |
| High (vulnerability in dependency) | Patch within 48 hours. Release as next patch version. |
| Medium / Low | Bundle into the next planned minor release. |
Consumer impact assessment¶
Before any release, answer these questions:
- Which repositories consume this workflow? (Search for
uses:references across organizations.) - Which version tags do they pin to?
- Will this change alter behavior for consumers on floating tags?
Tip
Use gh search code "uses: cockpit-org/.github/.github/workflows/build.yml" to find all consumers across the enterprise.
Usage monitoring¶
Track workflow adoption and version distribution:
- Number of repositories consuming each workflow
- Version distribution (how many repos are on
v1vsv2) - Failure rates per workflow and version
- Average execution time trends
Workflow catalog management¶
Catalog index¶
Maintain a top-level README.md in the workflow repository listing every available workflow:
| Workflow | Purpose | Latest version | Status |
|---|---|---|---|
build.yml |
Compile and produce artifacts | v1.3.0 |
Active |
test.yml |
Run unit and integration tests | v1.2.1 |
Active |
security-scan.yml |
SAST, secrets, dependency scanning | v2.0.0 |
Active |
release.yml |
Tag, build, publish to registry | v1.1.0 |
Active |
dependency-update.yml |
Automated dependency PRs | v1.0.2 |
Active |
Feature request process¶
Product teams request new workflows or changes to existing ones through the cockpit organization's issue tracker:
- Team opens an issue using the workflow request template.
- Platform Team triages: accept, defer, or suggest an alternative.
- Accepted requests enter the backlog and follow the standard authoring process.
- The requesting team is tagged as a reviewer on the implementation PR.
Anti-patterns¶
God workflow¶
Problem: A single workflow handles build, test, scan, release, and notification in one file with dozens of inputs and conditional logic.
Why it breaks: Every change risks side effects across unrelated stages. Testing becomes combinatorial. Consumers cannot opt into individual steps.
Fix: One workflow per responsibility. Consumers compose them in their caller file.
Unversioned releases¶
Problem: The team pushes changes to main without creating version tags.
Why it breaks: Consumers on @v1 do not receive updates. Consumers who reference @main (which they should not) get untested changes immediately.
Fix: Every merge to main that affects workflow behavior must produce a tagged release.
No testing¶
Problem: Workflow changes are merged based on code review alone, without running them against test repositories.
Why it breaks: YAML syntax errors, missing input handling, and permission issues only surface when real consumers break.
Fix: Require the test matrix to pass before merging any workflow PR.
Breaking changes without major version bump¶
Problem: An input is renamed or a default is changed in a patch release.
Why it breaks: Every consumer on the floating tag (@v1) breaks on their next run with no warning.
Fix: Follow semver strictly. Any consumer-facing behavioral change that is not purely additive requires a major version bump.
Next: Consume the framework