Skip to content

Commit 0ec4f11

Browse files
feat: add per-job content-addressed caching composite action
Add reusable composite action that queries GitHub Checks API to determine if a job already succeeded for the current commit SHA. Enables per-job intelligent execution decisions based on: - Execution history via GitHub Checks API query - Path-based change detection with configurable regex filters - Force-run override capability This composite action provides the foundation for eliminating centralized workflow coordination jobs and achieving fine-grained per-job and per-matrix-element caching.
1 parent 9295c88 commit 0ec4f11

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
name: Cached CI Job
2+
description: Execute job only if not already successful for this commit SHA
3+
4+
inputs:
5+
check-name:
6+
description: Full check run name (defaults to github.job, include matrix values for matrix jobs)
7+
required: false
8+
default: ${{ github.job }}
9+
path-filters:
10+
description: Regex pattern for relevant file paths (empty means always relevant)
11+
required: false
12+
default: ''
13+
force-run:
14+
description: Force execution even if already successful
15+
required: false
16+
default: 'false'
17+
18+
outputs:
19+
should-run:
20+
description: Whether job should execute (true/false)
21+
value: ${{ steps.decide.outputs.should-run }}
22+
previously-succeeded:
23+
description: Whether this job previously succeeded for this commit
24+
value: ${{ steps.check-history.outputs.previously-succeeded }}
25+
relevant-changes:
26+
description: Whether relevant file changes were detected
27+
value: ${{ steps.check-paths.outputs.relevant-changes }}
28+
29+
runs:
30+
using: composite
31+
steps:
32+
- name: Query GitHub Checks API for execution history
33+
id: check-history
34+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # ratchet:actions/github-script@v7
35+
env:
36+
CHECK_NAME: ${{ inputs.check-name }}
37+
with:
38+
script: |
39+
const checkName = process.env.CHECK_NAME;
40+
const commit = context.sha;
41+
42+
core.info(`Querying execution history for: "${checkName}" @ ${commit}`);
43+
44+
try {
45+
const { data: checks } = await github.rest.checks.listForRef({
46+
owner: context.repo.owner,
47+
repo: context.repo.repo,
48+
ref: commit,
49+
check_name: checkName,
50+
});
51+
52+
core.info(`Found ${checks.check_runs.length} check run(s) for this commit`);
53+
54+
// Find any completed successful run
55+
const successfulRun = checks.check_runs.find(run =>
56+
run.conclusion === 'success' &&
57+
run.status === 'completed'
58+
);
59+
60+
if (successfulRun) {
61+
core.info(`✓ Job already succeeded: ${successfulRun.html_url}`);
62+
core.setOutput('previously-succeeded', 'true');
63+
} else {
64+
const failedRuns = checks.check_runs.filter(run =>
65+
run.conclusion === 'failure' &&
66+
run.status === 'completed'
67+
);
68+
if (failedRuns.length > 0) {
69+
core.info(`✗ Found ${failedRuns.length} previous failed run(s)`);
70+
} else {
71+
core.info(`✗ No previous runs found`);
72+
}
73+
core.setOutput('previously-succeeded', 'false');
74+
}
75+
} catch (error) {
76+
core.warning(`API query failed: ${error.message}`);
77+
core.setOutput('previously-succeeded', 'false');
78+
}
79+
80+
- name: Check for relevant file changes
81+
id: check-paths
82+
if: |
83+
steps.check-history.outputs.previously-succeeded != 'true' &&
84+
inputs.path-filters != ''
85+
shell: bash
86+
env:
87+
PATH_FILTERS: ${{ inputs.path-filters }}
88+
run: |
89+
# Determine base ref for comparison
90+
if [ "${{ github.event_name }}" = "pull_request" ]; then
91+
BASE_REF="${{ github.event.pull_request.base.sha }}"
92+
else
93+
BASE_REF="HEAD^"
94+
fi
95+
96+
echo "Checking for changes matching: $PATH_FILTERS"
97+
echo "Comparing $BASE_REF...HEAD"
98+
99+
# Workflow changes always trigger all jobs
100+
if git diff --name-only "$BASE_REF" HEAD | grep -qE '\.github/workflows/'; then
101+
echo "relevant-changes=true" >> $GITHUB_OUTPUT
102+
echo "✓ Workflow changes detected - job is relevant"
103+
exit 0
104+
fi
105+
106+
# Check if any relevant files changed
107+
if git diff --name-only "$BASE_REF" HEAD | grep -qE "$PATH_FILTERS"; then
108+
echo "relevant-changes=true" >> $GITHUB_OUTPUT
109+
echo "✓ Relevant file changes detected"
110+
else
111+
echo "relevant-changes=false" >> $GITHUB_OUTPUT
112+
echo "✗ No relevant file changes"
113+
fi
114+
115+
- name: Make execution decision
116+
id: decide
117+
shell: bash
118+
env:
119+
FORCE: ${{ inputs.force-run }}
120+
PREV_SUCCESS: ${{ steps.check-history.outputs.previously-succeeded }}
121+
HAS_CHANGES: ${{ steps.check-paths.outputs.relevant-changes }}
122+
HAS_FILTERS: ${{ inputs.path-filters != '' }}
123+
run: |
124+
echo "=== Execution Decision ==="
125+
echo "Force run: $FORCE"
126+
echo "Previously succeeded: $PREV_SUCCESS"
127+
echo "Has path filters: $HAS_FILTERS"
128+
echo "Relevant changes: $HAS_CHANGES"
129+
echo ""
130+
131+
# Force run overrides everything
132+
if [ "$FORCE" = "true" ]; then
133+
echo "should-run=true" >> $GITHUB_OUTPUT
134+
echo "Decision: RUN (forced by input)"
135+
exit 0
136+
fi
137+
138+
# If already succeeded for this commit, skip
139+
if [ "$PREV_SUCCESS" = "true" ]; then
140+
echo "should-run=false" >> $GITHUB_OUTPUT
141+
echo "Decision: SKIP (already succeeded for this commit)"
142+
exit 0
143+
fi
144+
145+
# If we have path filters, honor them
146+
if [ "$HAS_FILTERS" = "true" ]; then
147+
echo "should-run=${HAS_CHANGES}" >> $GITHUB_OUTPUT
148+
if [ "$HAS_CHANGES" = "true" ]; then
149+
echo "Decision: RUN (relevant file changes detected)"
150+
else
151+
echo "Decision: SKIP (no relevant file changes)"
152+
fi
153+
else
154+
# No filters means always run if not already succeeded
155+
echo "should-run=true" >> $GITHUB_OUTPUT
156+
echo "Decision: RUN (no path filters specified)"
157+
fi

0 commit comments

Comments
 (0)