File size: 43,916 Bytes
699da45
45af64a
 
 
 
224a826
45af64a
 
 
 
 
 
 
 
 
 
 
 
 
 
224a826
45af64a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224a826
45af64a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224a826
 
 
45af64a
 
224a826
45af64a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
699da45
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Github actions Runtime 2026 — check your workflow free</title>
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/action-runtime-sunset-lint"></head>
<body>
<div class="wrap">
<h1>Github actions Runtime 2026 — check your workflow free</h1>
<p class="lede">Finds GitHub Actions steps whose JS runtime is retired or archived, with the exact version to move to and the days left before the workflow stops running. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">name: release
on:
  push:
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '16'
          cache: npm
      - run: npm ci &amp;&amp; npm run build
      - name: publish the version we just built
        run: echo "::set-output name=version::$(node -p 'require(\"./package.json\").version')"
      - uses: actions/upload-artifact@v3
        with:
          name: dist
          path: dist/</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> Lints the workflow YAML you have open and names the replacement version and the days left for every retired step in it.. <strong>In the editor:</strong> Scans every workflow and composite action file in the repository in one pass and exports a dated migration checklist you keep. — that part asks for a licence key.<br>
<a href="https://getreadystack.com/api/buy/cl/polar_cl_jT1WSh3ks3YVOX7etB8r9YixHfNivbgp7pfOh3eXlj8" style="display:inline-block;margin-top:10px;padding:9px 14px;border-radius:8px;background:#1f6feb;color:#fff;text-decoration:none;font-weight:600">Whole-repo sweep + exported migration checklist — $29 once</a>
<div class="note">$29 once &middot; one licence key per person or CI seat &middot; A DevOps contractor doing the same audit by hand bills $30 to $150 an hour.</div></div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var ARS_RULES = [
  {"id": "checkout-node12", "sev": "high", "state": "dead", "re": "uses:\\s*actions/checkout@v[12](\\.[0-9.]+)?\\s*$", "msg": "actions/checkout@v1/v2 runs on the node12 runtime, which GitHub-hosted runners no longer provide.", "fix": "actions/checkout@v5"},
  {"id": "checkout-node16", "sev": "high", "state": "dead", "re": "uses:\\s*actions/checkout@v3(\\.[0-9.]+)?\\s*$", "msg": "actions/checkout@v3 runs on the node16 runtime, which is removed from GitHub-hosted runners.", "fix": "actions/checkout@v5"},
  {"id": "setup-node-node16", "sev": "high", "state": "dead", "re": "uses:\\s*actions/setup-node@v[123](\\.[0-9.]+)?\\s*$", "msg": "actions/setup-node@v1/v2/v3 runs on the node16 runtime, which is removed from GitHub-hosted runners.", "fix": "actions/setup-node@v5"},
  {"id": "cache-node16", "sev": "high", "state": "dead", "re": "uses:\\s*actions/cache@v[123](\\.[0-9.]+)?\\s*$", "msg": "actions/cache@v1/v2/v3 runs on the node16 runtime, which is removed from GitHub-hosted runners.", "fix": "actions/cache@v4"},
  {"id": "upload-artifact-closed", "sev": "high", "state": "dead", "re": "uses:\\s*actions/upload-artifact@v[123](\\.[0-9.]+)?\\s*$", "msg": "actions/upload-artifact@v3 and older call an artifact backend that was switched off on 2025-01-30; the step fails, it does not warn.", "fix": "actions/upload-artifact@v4"},
  {"id": "download-artifact-closed", "sev": "high", "state": "dead", "re": "uses:\\s*actions/download-artifact@v[123](\\.[0-9.]+)?\\s*$", "msg": "actions/download-artifact@v3 and older call the artifact backend that was switched off on 2025-01-30.", "fix": "actions/download-artifact@v6"},
  {"id": "github-script-node16", "sev": "high", "state": "dead", "re": "uses:\\s*actions/github-script@v[1-6](\\.[0-9.]+)?\\s*$", "msg": "actions/github-script@v6 and older run on the node16 runtime, which is removed from GitHub-hosted runners.", "fix": "actions/github-script@v8"},
  {"id": "setup-python-node16", "sev": "high", "state": "dead", "re": "uses:\\s*actions/setup-python@v[1-4](\\.[0-9.]+)?\\s*$", "msg": "actions/setup-python@v4 and older run on the node16 runtime, which is removed from GitHub-hosted runners.", "fix": "actions/setup-python@v6"},
  {"id": "setup-java-node16", "sev": "high", "state": "dead", "re": "uses:\\s*actions/setup-java@v[123](\\.[0-9.]+)?\\s*$", "msg": "actions/setup-java@v3 and older run on the node16 runtime, which is removed from GitHub-hosted runners.", "fix": "actions/setup-java@v5"},
  {"id": "setup-dotnet-node16", "sev": "high", "state": "dead", "re": "uses:\\s*actions/setup-dotnet@v[123](\\.[0-9.]+)?\\s*$", "msg": "actions/setup-dotnet@v3 and older run on the node16 runtime, which is removed from GitHub-hosted runners.", "fix": "actions/setup-dotnet@v5"},
  {"id": "create-release-archived", "sev": "high", "state": "archived", "re": "uses:\\s*actions/create-release@", "msg": "actions/create-release is an archived repository: no runtime bump will ever land there, so the step dies with its runtime.", "fix": "softprops/action-gh-release@v2 or 'gh release create'"},
  {"id": "release-asset-archived", "sev": "high", "state": "archived", "re": "uses:\\s*actions/upload-release-asset@", "msg": "actions/upload-release-asset is an archived repository and takes no further runtime updates.", "fix": "softprops/action-gh-release@v2 or 'gh release create'"},
  {"id": "using-node12", "sev": "high", "state": "dead", "re": "using:\\s*['\"]?node12", "msg": "This composite/JS action declares the node12 runtime, which GitHub-hosted runners no longer provide.", "fix": "using: 'node24'"},
  {"id": "using-node16", "sev": "high", "state": "dead", "re": "using:\\s*['\"]?node16", "msg": "This composite/JS action declares the node16 runtime, which is removed from GitHub-hosted runners.", "fix": "using: 'node24'"},
  {"id": "using-node20", "sev": "med", "state": "due", "due": "2026-09-23", "re": "using:\\s*['\"]?node20", "msg": "This action declares the node20 runtime, which GitHub retires on 2026-09-23.", "fix": "using: 'node24'"},
  {"id": "runner-ubuntu-retired", "sev": "high", "state": "dead", "re": "(runs-on|- )\\s*:?\\s*['\"]?ubuntu-(16\\.04|18\\.04|20\\.04)", "msg": "This runner image is retired; a job that asks for it is queued against a label that no longer exists.", "fix": "runs-on: ubuntu-24.04"},
  {"id": "runner-macos-retired", "sev": "high", "state": "dead", "re": "(runs-on|- )\\s*:?\\s*['\"]?macos-(10\\.15|11|12)", "msg": "This macOS runner image is retired and its label no longer resolves.", "fix": "runs-on: macos-15"},
  {"id": "runner-windows-retired", "sev": "high", "state": "dead", "re": "(runs-on|- )\\s*:?\\s*['\"]?windows-(2016|2019)", "msg": "This Windows runner image is retired and its label no longer resolves.", "fix": "runs-on: windows-2025"},
  {"id": "cmd-set-output", "sev": "med", "state": "dead", "re": "::set-output", "msg": "The set-output workflow command is disabled; the value silently never reaches the next step.", "fix": "echo \"name=value\" \u003e\u003e \"$GITHUB_OUTPUT\""},
  {"id": "cmd-save-state", "sev": "med", "state": "dead", "re": "::save-state", "msg": "The save-state workflow command is disabled and the state is dropped.", "fix": "echo \"name=value\" \u003e\u003e \"$GITHUB_STATE\""},
  {"id": "cmd-set-env", "sev": "high", "state": "dead", "re": "::(set-env|add-path)", "msg": "The set-env and add-path workflow commands were removed after CVE-2020-15228; the runner refuses them.", "fix": "echo \"NAME=value\" \u003e\u003e \"$GITHUB_ENV\""},
  {"id": "node-version-eol", "sev": "med", "state": "eol", "re": "node-version:\\s*['\"]?(12|14|16|18)([^0-9]|$)", "msg": "This Node major is past its own end-of-life, so the toolchain it installs gets no security fixes.", "fix": "node-version: '24'"},
  {"id": "floating-ref", "sev": "low", "state": "drift", "re": "uses:\\s*[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@(master|main)\\s*$", "msg": "A step pinned to a moving branch changes runtime under you without a version bump, so this sunset can arrive unannounced.", "fix": "pin a tag or a commit SHA"}
]
;</script>
<script>// Action Runtime Sunset Lint — one brain, used by the editor extension and by the free web page.
var RULES = (typeof module !== 'undefined' && module.exports) ? require('./rules.json') : window.ARS_RULES;

// The runtime clock this linter is built around: GitHub retires the node20 action runtime on 2026-09-23.
var SUNSET = { runtime: 'node20', date: '2026-09-23', successor: 'node24' };

function dayDiff(from, to) {
  var a = Date.parse(from + 'T00:00:00Z'), b = Date.parse(to + 'T00:00:00Z');
  if (isNaN(a) || isNaN(b)) return null;
  return Math.round((b - a) / 86400000);
}

function check(text, opts) {
  opts = opts || {};
  var today = opts.today || new Date().toISOString().slice(0, 10);
  var lines = String(text == null ? '' : text).split(/\r?\n/);
  var findings = [], counts = { dead: 0, due: 0, aging: 0 };

  for (var i = 0; i < lines.length; i++) {
    var line = lines[i];
    if (/^\s*#/.test(line)) continue;
    for (var r = 0; r < RULES.length; r++) {
      var rule = RULES[r];
      if (!new RegExp(rule.re).test(line)) continue;

      var state = rule.state, sev = rule.sev, left = null;
      if (state === 'due' && rule.due) {
        left = dayDiff(today, rule.due);
        if (left !== null && left <= 0) { state = 'dead'; sev = 'high'; }
      }
      var msg = rule.msg + ' Move to: ' + rule.fix + '.';
      if (left !== null) {
        msg = (left > 0 ? 'In ' + left + ' day' + (left === 1 ? '' : 's') + ': ' : 'Already retired: ') + msg;
      }
      findings.push({
        check: rule.id, sev: sev, msg: msg, line: i + 1,
        state: state, fix: rule.fix, days_left: left,
        evidence: line.replace(/^\s+/, '').slice(0, 120)
      });
      if (state === 'dead' || state === 'archived') counts.dead++;
      else if (state === 'due') counts.due++;
      else counts.aging++;
    }
  }

  var toSunset = dayDiff(today, SUNSET.date);
  return {
    findings: findings,
    counts: counts,
    rule_count: RULES.length,
    today: today,
    sunset: SUNSET.date,
    sunset_runtime: SUNSET.runtime,
    days_to_sunset: toSunset,
    verdict: findings.length === 0 ? 'green' : (counts.dead ? 'red' : 'amber')
  };
}

var API = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length, SUNSET: SUNSET };
if (typeof module !== 'undefined' && module.exports) module.exports = API;
if (typeof window !== 'undefined') window.ARSENGINE = API;
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
  const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
  return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
  const f = (res && res.findings) || [];
  const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
  f.forEach(function (x) { lines.push('  ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + '  ' + (x.line ? 'L' + x.line + '  ' : '') + (x.check || x.id || '') + '  ' + (x.msg || x.message || '')); });
  return lines.join('\n');
}
function toHtml(res) {
  const f = (res && res.findings) || [];
  if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
  const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
  return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var ARSREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = ARSREPORT;
else window.ARSREPORT = ARSREPORT;
</script>
<script>
(function(){
  var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
  today.value=new Date().toISOString().slice(0,10);
  function run(){ try { var res=window.ARSENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.ARSREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
  go.onclick=run; if (src.value.trim()) run();
})();
</script>


<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://marketplace.visualstudio.com/items?itemName=ReadyStack.action-runtime-sunset-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'action-runtime-sunset-lint',src:'mp',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">VS Code Marketplace</a><a href="https://www.npmjs.com/package/@readystack/action-runtime-sunset-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'action-runtime-sunset-lint',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://www.npmjs.com/package/@readystack/action-runtime-sunset-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'action-runtime-sunset-lint',src:'mcp',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">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/action-runtime-sunset-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'action-runtime-sunset-lint',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">ext install ReadyStack.action-runtime-sunset-lint
npx @readystack/action-runtime-sunset-lint &lt;file&gt;
npx @readystack/action-runtime-sunset-lint --mcp</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/team" 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 the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 23 rules Action Runtime Sunset Lint checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. 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">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</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:"action-runtime-sunset-lint",lang:"en",source:"rule_table"+((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">Finds the GitHub Actions steps that stop running on 2026-09-23, with the version to move to</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A DevOps contractor doing the same audit and migration by hand bills $30 to $150 an hour on the open freelance market.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_jT1WSh3ks3YVOX7etB8r9YixHfNivbgp7pfOh3eXlj8" 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>
<!--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>
<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><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 {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[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><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><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-22">Updated 2026-09-22</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-22","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p>Six findings in one 20-line release workflow, five already dead - that is what 23 dated rules report on the sample pipeline shipped with this linter, the same file shape CI engineers on GitHub-hosted runners push daily.</p>
<p>A GitHub Actions step does not warn you when its JavaScript runtime disappears. It fails. GitHub retires the node20 action runtime on 2026-09-23; node16 and node12 are already gone. The labels ubuntu-20.04, macos-12 and windows-2019 no longer resolve. The v3 artifact backend was switched off on 2025-01-30, so actions/upload-artifact@v3 does not degrade, it errors. And an archived action repository such as actions/create-release will never receive a runtime bump at all.</p>
<p>Nothing in your repository changes on the retirement date. The pipeline breaks on the next push - which, for a release workflow, is the push that was supposed to ship something.</p>
<h3>What the sample file says</h3>
<p>_fixtures/dirty.yml is an ordinary release workflow: tag trigger, build, publish an artifact. It was not written badly, just a while ago. Six lines come back:</p>
<p>L8   HIGH  runs-on: ubuntu-20.04              -&gt; runs-on: ubuntu-24.04 L10  HIGH  the repository clone step at v3    -&gt; the v5 pin L11  HIGH  actions/setup-node@v3 (node16)     -&gt; actions/setup-node@v5 L13  MED   node-version: &#x27;16&#x27;                 -&gt; node-version: &#x27;24&#x27; L17  MED   set-output is disabled             -&gt; &gt;&gt; &quot;$GITHUB_OUTPUT&quot; L18  HIGH  actions/upload-artifact@v3         -&gt; actions/upload-artifact@v4</p>
<p>Five of those six are already dead on a hosted runner today; the sixth is a toolchain pin past its own end of life. The migrated copy, _fixtures/clean.yml, reports 0 findings - the linter is not allergic to YAML.</p>
<h3>The date is an input</h3>
<p>Most deprecation lists are frozen prose; this one takes today&#x27;s date as an argument. With today = 2026-09-21, a composite action that declares using: node20 reads &quot;In 2 days: this action declares the node20 runtime, which GitHub retires on 2026-09-23. Move to: using: &#x27;node24&#x27;.&quot; On 2026-09-23 that line flips to &quot;Already retired&quot; and from medium to high severity. Change the input, and the answer changes - that is the difference between a linter and a blog post.</p>
<h3>Why the obvious free options miss it</h3>
<p>A chat assistant cannot see your .github/workflows directory, and the retirement dates it knows are the ones inside its training window - precisely the values that move. AI-written workflows still emit @v3 pins and set-output, because that was the common shape in the corpus. GitHub&#x27;s own deprecation notices do exist, but they arrive as annotations inside a run log - you have to trigger the run that fails.</p>
<h3>Twenty-three rules, grouped by how the step breaks</h3>
<p>Retired action runtimes at their old majors. Switched-off artifact backends. Archived action repositories. Declared runtimes in action.yml: node12 and node16 gone, node20 dated. Retired runner images across Ubuntu, macOS and Windows. Disabled workflow commands: set-output, save-state, set-env, add-path. Pins floating on @main. Every finding carries the replacement, not just the complaint.</p>
<h3>Free, and then the other axis</h3>
<p>Free and keyless: open a workflow file, run the check, get all 23 rules with replacement versions. One file, finished answer. The full version does a different job: it sweeps every workflow and composite action.yml in the workspace in one pass and writes a dated migration checklist into the repository. $29 once, one licence key per person or CI seat, 7-day full refund. For comparison, a DevOps contractor doing the same audit and migration by hand bills $30 to $150 an hour on the open freelance market.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku159172_short.mp4" type="video/mp4"></video><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 Action Runtime Sunset Lint 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 a composite action.yml and applies 23 dated rules to every line. Each finding names the exact replacement: actions/setup-node@v3 becomes actions/setup-node@v5, ubuntu-20.04 becomes ubuntu-24.04, and set-output becomes an append to $GITHUB_OUTPUT. It also prints the days left before the node20 action runtime retires on 2026-09-23.</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">CI and DevOps engineers who own a repository&#x27;s .github/workflows directory on GitHub-hosted runners, plus maintainers of published composite or JavaScript actions. It is aimed at people who ship releases from Actions, where a dead step blocks the release job itself rather than a test run. Reviewers of AI-generated pipeline YAML are the other case.</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 will a chat assistant not do this for me?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A chat assistant cannot read your .github/workflows directory, and runtime and runner-image retirement dates keep moving after its training window. GitHub&#x27;s own deprecation notices arrive as annotations inside a run log you have to trigger first. This linter reads the file in your editor against a dated table, so you get the answer before the push that would have failed.</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 key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free and keyless: open one workflow file, run the check, and get all 23 rules applied with replacement versions. On the sample release workflow in this repository that is 6 findings, 5 of them already dead. The $29 full version adds a different axis: every workflow and composite action in the repository in one pass, plus a dated migration checklist written into the repo.</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 DevOps contractor doing the same audit and migration by hand bills $30 to $150 an hour on the open freelance market, and a broken release pipeline is normally discovered by the release itself. The licence is $29 once, one key per person or CI seat,.</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 ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Action Runtime Sunset Lint checks the file you open against 23 rules from a rule set dated 2026-09-21, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Action Runtime Sunset Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads a GitHub Actions workflow or a composite action.yml and applies 23 dated rules to every line. Each finding names the exact replacement: actions/setup-node@v3 becomes actions/setup-node@v5, ubuntu-20.04 becomes ubuntu-24.04, and set-output becomes an append to $GITHUB_OUTPUT. It also prints the days left before the node20 action runtime retires on 2026-09-23."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "CI and DevOps engineers who own a repository's .github/workflows directory on GitHub-hosted runners, plus maintainers of published composite or JavaScript actions. It is aimed at people who ship releases from Actions, where a dead step blocks the release job itself rather than a test run. Reviewers of AI-generated pipeline YAML are the other case."}}, {"@type": "Question", "name": "Why will a chat assistant not do this for me?", "acceptedAnswer": {"@type": "Answer", "text": "A chat assistant cannot read your .github/workflows directory, and runtime and runner-image retirement dates keep moving after its training window. GitHub's own deprecation notices arrive as annotations inside a run log you have to trigger first. This linter reads the file in your editor against a dated table, so you get the answer before the push that would have failed."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Free and keyless: open one workflow file, run the check, and get all 23 rules applied with replacement versions. On the sample release workflow in this repository that is 6 findings, 5 of them already dead. The $29 full version adds a different axis: every workflow and composite action in the repository in one pass, plus a dated migration checklist written into the repo."}}, {"@type": "Question", "name": "What would this cost me otherwise?", "acceptedAnswer": {"@type": "Answer", "text": "A DevOps contractor doing the same audit and migration by hand bills $30 to $150 an hour on the open freelance market, and a broken release pipeline is normally discovered by the release itself. The licence is $29 once, one key per person or CI seat,."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Action Runtime Sunset Lint checks the file you open against 23 rules from a rule set dated 2026-09-21, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files."}}]}</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:159172,slug:"action-runtime-sunset-lint",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/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/ssdf-attestation-workflow-audit"><span class="yj-rel-t">SSDF Attestation Audit for GitHub Actions</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/agentforce-action-audit"><span class="yj-rel-t">Agentforce Action Audit</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/runtime-eol-deploy-block-audit"><span class="yj-rel-t">Runtime EOL and Deploy-Block Audit for Docker, CI and AWS Lambda</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/mdr-legacy-sunset-clock"><span class="yj-rel-t">MDR Legacy Sunset Clock 2026</span><span class="yj-rel-p">$60</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></body>
</html>