GitHub Actions unrecognized named-value: env and the context table
GitHub Actions unrecognized named-value: env is the most common member of a family, and the fix is never in the expression. Every workflow key accepts a fixed list of contexts, and a name outside that list is rejected before the run is queued, however correct the expression looks.

What this error means
The workflow is invalid and nothing runs. The message names the context in quotes, gives its position inside the expression, and prints the expression back to you, which makes the whole thing read like a typo even though the spelling is right. Two details give the real shape away. The same name works elsewhere in the same file, usually one key higher or one key lower, and the name that is rejected is almost always env or secrets. Both are contexts that exist, are documented, and are simply not permitted in the key where you used them. A long annotation with several of these separated by commas means the parser found every one of them in a single pass, so fixing the first line will not shorten the list.
The workflow is not valid. .github/workflows/dev.yml (Line: 15, Col: 11):
Unrecognized named-value: 'env'. Located at position 1 within expression: env.aws_accountA minimal workflow that produces it
This file is written for this page and has never been run. It defines a workflow-level environment variable and then uses it in two keys that do not accept the env context. Both are rejected in one annotation, which is why a single mistake in this family often prints as several.
name: ci
on: [push]
env:
runner_label: ubuntu-latest
jobs:
build:
name: build on ${{ env.runner_label }}
runs-on: ${{ env.runner_label }}
steps:
- run: echo buildingCommon causes
env used in a key that is evaluated before the runner exists
The largest group by far: runs-on, jobs.<job_id>.if, jobs.<job_id>.name, a job-level concurrency, or the with: of a called workflow. All of them are decided while the job is still being planned, and the environment belongs to a runner that has not been picked yet.
secrets used in a condition
A job-level or step-level if does not accept the secrets context, by design rather than by omission. The documented replacement is to copy the secret into a job environment variable and test that, which also gives you a reliable way to ask whether a secret is set at all.
matrix or strategy outside a matrix job
Both contexts exist only for a job that declares a strategy, so referencing them from a job without one, or from a workflow-level key, is an unrecognized name. In our experience this arrives when a matrix is factored out of one job and the conditions that referenced it are left behind.
steps read from a job-level key
The steps context belongs to the job that is running and holds only steps "that have an id specified and have already run". A job-level if, a runs-on or a with: in a caller cannot see it, because at that point no step has run. Job outputs and the needs context are the way to move a value up a level.
How to fix it
Look up the key, not the expression
- Take the workflow key from the line and column in the message, not the context name.
- Find that key in the context availability table and read the list of contexts it accepts.
- If the name you used is missing, move the value rather than rewriting the expression.
Use a repository variable where you wanted a workflow env
The vars context is available in nearly every key in the table, including runs-on, a job-level if and a caller with:. A value that is configuration rather than a per-run computation belongs there, and moving it removes the whole class of problem rather than working around one instance.
jobs:
build:
runs-on: ${{ vars.RUNNER_LABEL }}
steps:
- run: echo buildingCompute it in a job and read it through needs
When the value really is computed at run time, produce it as a job output and read it from a later job. The needs context is available in every job-level key that matters, including runs-on, so this is the general answer for a dynamic runner label or a dynamic condition.
jobs:
plan:
runs-on: ubuntu-latest
outputs:
label: ${{ steps.pick.outputs.label }}
steps:
- id: pick
run: echo "label=ubuntu-latest" >> "$GITHUB_OUTPUT"
build:
needs: plan
runs-on: ${{ needs.plan.outputs.label }}
steps:
- run: echo buildingPush the condition down to the step
A step-level if accepts env, steps, job and runner, which is almost everything a job-level one refuses. If the only reason the condition was at job level was tidiness, moving it down is the smallest change that works, and it keeps the job green rather than skipped.
The table that decides it
The contexts reference is unambiguous about what the table means: "The following table lists the restrictions on where each context and special function can be used within a workflow. The listed contexts are only available for the given workflow key, and may not be used anywhere else."
These are the rows that produce almost every report. Read the key you used in the left column, and if the name in your expression is not in the middle column, the parser will reject it no matter how the expression is written.
| Workflow key | Contexts available | Special functions |
|---|---|---|
env | github, secrets, inputs, vars | None |
jobs.<job_id>.if | github, needs, vars, inputs | always, cancelled, success, failure |
jobs.<job_id>.runs-on | github, needs, strategy, matrix, vars, inputs | None |
jobs.<job_id>.env | github, needs, strategy, matrix, vars, secrets, inputs | None |
jobs.<job_id>.with.<with_id> | github, needs, strategy, matrix, inputs, vars | None |
jobs.<job_id>.steps.if | github, needs, strategy, matrix, job, runner, env, vars, steps, inputs | always, cancelled, success, failure, hashFiles |
jobs.<job_id>.steps.run | github, needs, strategy, matrix, job, runner, env, vars, secrets, steps, inputs | hashFiles |
Why env is missing from the interesting rows
A job-level condition is evaluated before the job is dispatched, and the environment does not exist yet at that point: it belongs to the runner that has not been chosen. The same reasoning covers runs-on, which is the key that chooses the runner in the first place. The step-level rows are different because by then a runner is running the job, which is why jobs.<job_id>.steps.if does accept env while jobs.<job_id>.if does not.
That is also why the same expression can work one line away from where it fails. actions/runner#2372 is the version of this that catches people moving to reusable workflows: a workflow-level env used in the with: of a called workflow, reported as "Unrecognized named-value: 'env'. Located at position 1 within expression". The variable is fine; the key does not take it.
Secrets in a condition, and the documented way round it
Secrets are a special case with an answer written into the workflow syntax reference: "Secrets cannot be directly referenced in if: conditionals. Instead, consider setting secrets as job-level environment variables, then referencing the environment variables to conditionally run steps in the job." The same passage adds the behavior you need for the check: "If a secret has not been set, the return value of an expression referencing the secret ... will be an empty string."
So the pattern is two steps. Lift the secret into the job environment, where the secrets context is available, then test the environment variable in a step-level condition, where the env context is available. The corrected workflow below does both, and it is the example the documentation itself uses.
jobs:
build:
runs-on: ubuntu-latest
env:
super_secret: ${{ secrets.SuperSecret }}
steps:
- if: ${{ env.super_secret != '' }}
run: echo 'the secret has a value'
- if: ${{ env.super_secret == '' }}
run: echo 'the secret is not set'Why there is no recorded run on this page
The parser rejects the file, so no job is ever created and there is no runner log to record. There is nothing transient here either: the same file fails the same way every time, so a retry changes nothing and no runner can repair it. The annotation quoted above comes from a public issue, and the workflows on this page are illustrative.
How to prevent it
- Keep the context availability table open while writing any job-level expression.
- Put configuration in repository or organization variables, where
varsis available almost everywhere. - Pass secrets through
env:at job or step level, never into a condition or a runner label. - Lint workflows with a tool that knows the table, so the rejection happens on commit rather than on push.
Frequently asked questions
What does Unrecognized named-value mean in GitHub Actions?
Why can I not use env in runs-on or a job-level if?
jobs.<job_id>.runs-on as accepting github, needs, strategy, matrix, vars and inputs, and jobs.<job_id>.if as accepting github, needs, vars and inputs. Use vars, or a job output read through needs.Why are secrets not allowed in an if condition?
if: conditionals. Instead, consider setting secrets as job-level environment variables, then referencing the environment variables to conditionally run steps in the job." An unset secret returns an empty string, which is what makes the environment test reliable.