Out of scope
High-demand GitHub Actions asks a compile-time transpiler structurally can't solve — and why.
Why document non-goals?
Four waves of community research (community discussions, actions/runner issues,
Reddit/SO, competitor authoring, and github/customer-feedback enterprise intake)
surfaced a recurring set of high-demand GitHub Actions asks that a compile-time
transpiler structurally cannot solve. Actio is a build-time compiler with no
runtime: it can't touch the platform or see values that only
exist while a run is executing.
This page is a reference so we (and contributors) stop re-litigating these, can answer "why doesn't Actio just…", and have honest talking points. Each item below is grouped by the structural root cause, with a representative high-demand issue linked.
Rule of thumb
If the answer depends on a value that exists only at runtime, or on how GitHub stores/scopes/renders something, Actio can't synthesize it. If it's authoring ergonomics over a compile-time-known value, it's in scope.
1. Runtime graph mutation
The job graph is fixed when the workflow is parsed. You can't add/remove/reorder jobs or edges based on values computed during the run.
Why not: these mutate the DAG from runtime state. Actio only knows the DAG at compile time. (Compile-time-known fan-out IS in scope — see edge cases.)
2. Platform security / secrets / identity
Secret storage, scoping, environments, branch protection, and marketplace trust live in the GitHub platform, not in YAML.
Why not: no amount of generated YAML changes where GitHub keeps secrets or enforces protection.
3. UI / run-experience rendering
How runs, jobs, and the Actions tab render is platform UI. YAML can't restyle it.
Why not: runtime-if skipping and tab rendering are UI behaviors.
(Compile-time-known skips ARE in scope via static-if —
see edge cases.)
4. Runtime expression / context availability
Some contexts and fields just aren't populated where users want them, at runtime.
Why not: when the value is runtime-input-driven, Actio can't inline it. (When the
value is compile-time-known, {{ params }} already inlines it
into runs-on:/with:/if: where ${{ env }} is rejected — see edge cases.)
5. Concurrency / scheduling semantics
The scheduler decides queueing, cancellation, and conclusions at runtime.
Why not: these are runtime scheduler behaviors. Generated YAML can't change how the queue/conclusion engine works.
6. Native-now — explicit non-goals (don't build)
GitHub shipped these (or close enough). Building macros for them would be redundant churn.
- actions/runner#409 — ternary / conditional operator → now native (
${{ ... && ... || ... }}, and a real conditional landing). Don't add expr-ternary sugar. - actions/runner#1182: YAML anchors shipped (changelog, 2025-09-18). Actio now flattens a
- *aliaswhose anchor is a step list into the surrounding steps, so native_anchors:+- *aliascover same-file, no-param step reuse. That makesfragmentsredundant for that case, so it is deprecated (still compiles, warning only). What anchors still can't do: take typed params or cross files. Those stay withtemplates:+inject ... withand cross-fileinject: ./lib#name. Merge keys remain unshipped upstream (request #185877); Actio accepts the 1.1 merge key (<<:) in source and erases it on emit, with an opt-instrictlint for pure-1.2.2 source.
Edge cases — the compile-time subset IS in scope
The line isn't the topic, it's when the value is known. Where a runtime limitation has a compile-time-known slice, Actio already (or should) cover it:
- Skipping jobs → runtime UI hide = platform; compile-time-known drop =
static-if✅ envin disallowed fields → runtime${{ env }}= platform; compile-time literal ={{ params }}interpolation ✅- Per-leg matrix
needs→ runtimefromJSONmatrix = platform; compile-time-known matrix =expand_matrix:(#97) - Typed/validated inputs → runtime coercion = platform; compile-time typing =
params✅
If a future ask lands on the compile-time side of one of these lines, it's a candidate — file it. Everything above stays out of scope by design.