Thank you for your interest in contributing to workflows!
- Git
- Basic understanding of GitHub Actions workflow syntax
- Familiarity with bash and
jqfor JSON processing
For external contributors (fork first):
# Fork the repo on GitHub, then:
git clone https://github.com/YOUR-USERNAME/workflows.git
cd workflows
git remote add upstream https://github.com/marcus-hooper/workflows.gitFor maintainers (direct access):
git clone https://github.com/marcus-hooper/workflows.git
cd workflowsWorkflows are tested by calling them from other repositories. No local test harness exists.
To test changes:
- Push your branch to your fork
- Call the workflow from a test repository using
uses: YOUR-USERNAME/workflows/.github/workflows/workflow-name.yml@your-branch - Verify the outputs match expected behavior
Before pushing, validate your workflow files:
# YAML syntax validation (install: https://github.com/mikefarah/yq)
yq eval '.' .github/workflows/*.yml > /dev/null
# GitHub Actions lint (install: https://github.com/rhysd/actionlint)
# Includes ShellCheck integration for bash scripts in run: blocks
actionlintNote:
actionlintautomatically runs ShellCheck on embedded bash scripts when ShellCheck is installed, so separate ShellCheck invocation is unnecessary.
All reusable workflows use workflow_call:
on:
workflow_call:
inputs:
input_name:
description: 'Description of the input'
required: false
default: '10'
type: string
outputs:
output_name:
description: 'Description of the output'
value: ${{ jobs.job-name.outputs.output_name }}Use heredoc syntax with EOF delimiters for multi-line GITHUB_OUTPUT:
echo "output_name<<EOF" >> "$GITHUB_OUTPUT"
echo "$json_content" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"Note: Always quote
"$GITHUB_OUTPUT"to handle paths with spaces. Never use the deprecated::set-outputsyntax.
Always use jq for safe JSON construction. Never use string concatenation:
# Correct - jq handles escaping
commit_json=$(jq -n --arg title "$time_ago" --arg value "$message" \
'{title: $title, value: $value}')
# Incorrect - unsafe string concatenation
commit_json="{\"title\": \"$time_ago\", \"value\": \"$message\"}"Format timestamps as relative time: Xs, Xm, Xh, Xd (seconds, minutes, hours, days).
- Use
ubuntu-latestfor runners - Ensure scripts are UTF-8 safe (handle special characters properly)
- Pin actions to full commit SHA with version comment (e.g.,
actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2) - Use appropriate
fetch-depth:0for full history (tags, blame), specific number for recent commits only
Every workflow in this repository must follow these security requirements:
| Practice | Requirement |
|---|---|
| Permissions | Start with permissions: {} at workflow level, grant minimal permissions per job |
| Harden Runner | First step in every job: step-security/harden-runner with egress-policy: block |
| Action Pinning | Pin to full commit SHA, never tags (supply chain security) |
| Checkout | Always set persist-credentials: false on actions/checkout |
| Timeouts | Always set timeout-minutes on jobs to prevent runaway workflows |
| Concurrency | Use concurrency.group to prevent duplicate runs |
Example secure job structure:
permissions: {} # At workflow level
jobs:
example:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read # Minimal per-job permissions
steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: block
allowed-endpoints: >
github.com:443
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false# Use set -e for error handling
set -e
# Quote variables to handle spaces/special characters
echo "$variable"
# Use [[ ]] for conditionals (bash-specific, safer)
if [[ -z "$var" ]]; then
echo "var is empty"
fi
# Check command success explicitly
if ! command -v jq &> /dev/null; then
echo "jq is required but not installed"
exit 1
fiVariable scope in loops: while read in a pipe runs in a subshell, so variables set inside won't persist:
# WRONG - $count will be 0 after the loop
count=0
echo -e "a\nb\nc" | while read -r line; do
((count++))
done
echo "$count" # Outputs: 0
# CORRECT - use process substitution to preserve variables
count=0
while read -r line; do
((count++))
done < <(echo -e "a\nb\nc")
echo "$count" # Outputs: 3| Topic | Guideline |
|---|---|
| UTF-8 encoding | Handle special characters in commit messages, branch names |
| JSON construction | Always use jq for escaping, never string concatenation |
| Output format | Microsoft Adaptive Cards for Teams integration |
| Shellcheck | All bash scripts should pass shellcheck -s bash |
When adding a new reusable workflow:
- Use
workflow_calltrigger with documented inputs/outputs - Add comprehensive input descriptions with defaults where appropriate
- Use
jqfor all JSON construction - Follow all Security Practices (harden-runner, pinned actions, minimal permissions)
- Set
timeout-minutesandconcurrencyon all jobs - Document the workflow in README.md with usage examples
- Test from another repository before submitting PR
Use Conventional Commits format:
<type>: <description>
[optional body]
[optional footer]
| Type | Description |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
refactor |
Code change that neither fixes a bug nor adds a feature |
test |
Adding or updating tests |
ci |
CI/workflow changes |
deps |
Dependency updates |
security |
Security improvements |
chore |
Other maintenance tasks |
perf |
Performance improvement |
feat: add workflow for PR labeling
fix: handle empty commit history gracefully
docs: update usage examples in README
ci: pin actions/checkout to SHA
deps: update actions/checkout to v4
security: add input validation for webhook URLs
For breaking changes, use ! after the type or add a BREAKING CHANGE: footer:
feat!: change output format from array to object
Output structure changed to support additional metadata.
-
Create a branch from
main:git checkout -b <type>/short-description
Use branch prefixes that match your commit type:
Branch Prefix Use For feature/New features fix/Bug fixes docs/Documentation changes refactor/Code refactoring ci/CI/workflow changes security/Security improvements -
Run CI locally (see above)
-
Test your workflow from another repository
-
Update CHANGELOG.md under
[Unreleased] -
Update README.md with new workflow documentation
| Requirement | Description |
|---|---|
| Tested | Workflow called successfully from test repository |
| Documented | README.md updated with usage, inputs, and outputs |
| Changelog | Entry added under [Unreleased] |
Security Checks (run automatically):
| Check | Workflow | Purpose |
|---|---|---|
| CodeQL | codeql.yml | Static security analysis for Actions |
| OSSF Scorecard | scorecard.yml | Supply chain security analysis |
| Dependency Review | On PR | Flags vulnerable dependencies |
Security Review Checklist (for reviewers):
- Actions pinned to full SHA (not tags)
-
permissions: {}at workflow level with minimal per-job grants -
step-security/harden-runneras first step in each job -
persist-credentials: falseon all checkout steps -
timeout-minutesset on all jobs - No secrets exposed in logs or outputs
-
jq --argused for all dynamic JSON construction
Include:
- Summary of changes
- Related issue (if any)
- Testing performed (which repository tested the workflow)
- All PRs require review before merge
- Address review feedback promptly
- Squash merge to
main
Use the bug report template. Include:
- Workflow name and version/ref used
- Calling workflow snippet
- Expected vs actual behavior
- Relevant error messages from workflow logs
Use the feature request template. Include:
- Problem statement
- Proposed solution
- Example usage
Releases are managed by maintainers:
- All CI checks pass on
main - CHANGELOG.md updated with version and date
- Tag created:
git tag -a v1.0.0 -m "Release v1.0.0" - Tag pushed:
git push origin v1.0.0 - GitHub Actions creates release and updates major version tag (
v1)
- Questions: Open a Discussion
- Bugs: Open an Issue using the bug report template
- Features: Open an Issue using the feature request template
- Security: See SECURITY.md for responsible disclosure
By contributing, you agree that your contributions will be licensed under the MIT License.