55 branches : [main, staging, dev]
66 pull_request :
77 branches : [main, staging, dev]
8+ # Docs content and markdown don't affect the app build or images; push
9+ # runs stay unfiltered because they feed the deploy pipeline.
10+ paths-ignore :
11+ - ' apps/docs/content/**'
12+ - ' **/*.md'
813
914concurrency :
1015 group : ci-${{ github.ref }}
2429 detect-version :
2530 name : Detect Version
2631 runs-on : blacksmith-4vcpu-ubuntu-2404
32+ timeout-minutes : 5
2733 if : github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/dev')
2834 outputs :
2935 version : ${{ steps.extract.outputs.version }}
7682 needs : [detect-version, migrate-dev]
7783 if : github.event_name == 'push' && github.ref == 'refs/heads/dev'
7884 runs-on : blacksmith-8vcpu-ubuntu-2404
85+ timeout-minutes : 30
7986 permissions :
8087 contents : read
8188 id-token : write
@@ -139,6 +146,7 @@ jobs:
139146 needs : [migrate-dev]
140147 if : github.event_name == 'push' && github.ref == 'refs/heads/dev'
141148 runs-on : blacksmith-4vcpu-ubuntu-2404
149+ timeout-minutes : 15
142150 steps :
143151 - name : Checkout code
144152 uses : actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
@@ -185,6 +193,7 @@ jobs:
185193 github.event_name == 'push' &&
186194 (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
187195 runs-on : blacksmith-8vcpu-ubuntu-2404
196+ timeout-minutes : 30
188197 permissions :
189198 contents : read
190199 packages : write
@@ -284,6 +293,7 @@ jobs:
284293 github.event_name == 'push' &&
285294 (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
286295 runs-on : blacksmith-2vcpu-ubuntu-2404
296+ timeout-minutes : 10
287297 permissions :
288298 contents : read
289299 id-token : write
@@ -298,19 +308,21 @@ jobs:
298308 id : login-ecr
299309 uses : aws-actions/amazon-ecr-login@d539f0932e70871a027e9d5a9d8fc38589180a64 # v2
300310
301- # Re-running this job from an old run would retag the deploy tags back
302- # to that run's commit and immediately trigger a production deploy of
303- # stale code. Only promote while this commit is still the branch head;
304- # when superseded, skip cleanly — the newer run's promotion covers it .
311+ # Deploy-tag moves must be monotonic: a re- run of an old run must never
312+ # retag latest/staging back to stale code. A superseded first-attempt
313+ # run still promotes — the ci-<ref> concurrency group executes runs
314+ # serially in commit order, so an ancestor of head is a forward deploy .
305315 - name : Guard against stale promotion
306316 id : guard
317+ env :
318+ GH_TOKEN : ${{ github.token }}
307319 run : |
308- HEAD_SHA="$(git ls-remote "https://github.com/${{ github.repository }}.git" "refs/heads/${GITHUB_REF_NAME}" | cut -f1)"
309- if [ "$HEAD_SHA" != "${{ github.sha }}" ]; then
310- echo "::warning::Skipping promotion of ${{ github.sha }}: ${GITHUB_REF_NAME} has moved to ${HEAD_SHA}. Moving the deploy tags to this commit would deploy stale code; push a revert commit to roll back instead."
311- echo "fresh=false" >> $GITHUB_OUTPUT
312- else
320+ STATUS="$(gh api "repos/${{ github.repository }}/compare/${{ github.sha }}...${GITHUB_REF_NAME}" --jq '.status' || echo "unknown")"
321+ if [ "$STATUS" = "identical" ] || { [ "$STATUS" = "ahead" ] && [ "${{ github.run_attempt }}" = "1" ]; }; then
313322 echo "fresh=true" >> $GITHUB_OUTPUT
323+ else
324+ echo "::warning::Skipping promotion of ${{ github.sha }} (branch compare: ${STATUS}, attempt ${{ github.run_attempt }}). Moving the deploy tags here could deploy stale code; push a revert commit to roll back instead."
325+ echo "fresh=false" >> $GITHUB_OUTPUT
314326 fi
315327
316328 - name : Promote images to deploy tags
@@ -351,6 +363,7 @@ jobs:
351363 build-ghcr-arm64 :
352364 name : Build ARM64 (GHCR Only)
353365 runs-on : blacksmith-8vcpu-ubuntu-2404-arm
366+ timeout-minutes : 30
354367 if : github.event_name == 'push' && github.ref == 'refs/heads/main'
355368 permissions :
356369 contents : read
@@ -399,9 +412,11 @@ jobs:
399412 create-ghcr-manifests :
400413 name : Create GHCR Manifests
401414 runs-on : blacksmith-2vcpu-ubuntu-2404
415+ timeout-minutes : 10
402416 needs : [promote-images, build-ghcr-arm64, detect-version]
403417 if : github.event_name == 'push' && github.ref == 'refs/heads/main'
404418 permissions :
419+ contents : read
405420 packages : write
406421 strategy :
407422 matrix :
@@ -419,18 +434,19 @@ jobs:
419434 username : ${{ github.repository_owner }}
420435 password : ${{ secrets.GITHUB_TOKEN }}
421436
422- # Same protection as promote-images: a re-run of an old run must not
423- # move the public latest tags back to a stale commit. Immutable tags
424- # (sha and version) are still published — only latest moves are gated.
437+ # Same monotonic guard as promote-images, applied to the public latest
438+ # tags only — immutable sha and version tags are always published.
425439 - name : Guard against stale latest tags
426440 id : guard
441+ env :
442+ GH_TOKEN : ${{ github.token }}
427443 run : |
428- HEAD_SHA="$(git ls-remote "https://github.com/${{ github.repository }}.git" "refs/heads/${GITHUB_REF_NAME}" | cut -f1)"
429- if [ "$HEAD_SHA" != "${{ github.sha }}" ]; then
430- echo "::warning::${GITHUB_REF_NAME} has moved to ${HEAD_SHA}; publishing immutable tags for ${{ github.sha }} but skipping the latest tags."
431- echo "fresh=false" >> $GITHUB_OUTPUT
432- else
444+ STATUS="$(gh api "repos/${{ github.repository }}/compare/${{ github.sha }}...${GITHUB_REF_NAME}" --jq '.status' || echo "unknown")"
445+ if [ "$STATUS" = "identical" ] || { [ "$STATUS" = "ahead" ] && [ "${{ github.run_attempt }}" = "1" ]; }; then
433446 echo "fresh=true" >> $GITHUB_OUTPUT
447+ else
448+ echo "::warning::Publishing immutable tags for ${{ github.sha }} but skipping the latest tags (branch compare: ${STATUS}, attempt ${{ github.run_attempt }})."
449+ echo "fresh=false" >> $GITHUB_OUTPUT
434450 fi
435451
436452 - name : Publish tags and manifests
@@ -462,6 +478,7 @@ jobs:
462478 check-docs-changes :
463479 name : Check Docs Changes
464480 runs-on : blacksmith-4vcpu-ubuntu-2404
481+ timeout-minutes : 5
465482 if : github.event_name == 'push' && github.ref == 'refs/heads/main'
466483 outputs :
467484 docs_changed : ${{ steps.filter.outputs.docs }}
@@ -490,6 +507,7 @@ jobs:
490507 create-release :
491508 name : Create GitHub Release
492509 runs-on : blacksmith-4vcpu-ubuntu-2404
510+ timeout-minutes : 10
493511 needs : [create-ghcr-manifests, detect-version]
494512 if : needs.detect-version.outputs.is_release == 'true'
495513 permissions :
0 commit comments