GitHub Actions unrecognized named-value: env and the context table
GitHub Actions unrecognized named-value: env means the expression sits in a workflow key whose list of allowed contexts does not include env, most often runs-on, jobs.<job_id>.if, a job name, or the with: of a reusable workflow call, all of which are evaluated before a runner exists.
The fix is to move the value, not reword the expression: use a vars variable, a job output read through needs, or a step-level if, which does accept env.
The rows people trip on
A job-level condition takes four contexts and four status functions, and neither env nor secrets is among them.
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.
Invalid workflow file, quoted from actions/runner#480
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_account
Common 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 in a workflow-level key
The workflow-level keys, run-name, the top-level env and a workflow-level concurrency, are read before any job exists, so neither matrix nor strategy is on their lists. Inside a job the parser does not check whether a strategy is declared: matrix.os in the runs-on of a job without a matrix is accepted and resolves to an empty value.
In our experience the rejected version arrives when a matrix value is promoted to a workflow-level env to share it between jobs.
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.
# .github/workflows/ci.yml (illustrative)
jobs:
build:
runs-on: ${{ vars.RUNNER_LABEL }}
steps:
- run: echo building
Compute 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.
# .github/workflows/ci.yml (illustrative)
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 building
Push 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.
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.
A 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.
# .github/workflows/ci.yml (illustrative)
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 building
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.
# .github/workflows/ci.yml, corrected (illustrative)
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.
When the token in quotes is punctuation rather than a context name, the message reads differently: GitHub Actions unexpected symbol in a ${{ }} expression.
FAQ
What does Unrecognized named-value mean in GitHub Actions?
That the expression parser met a name it is not allowed to resolve in that position. The name usually exists as a context; it is just not on the list for the key you used. The message quotes the name, gives a one-based position inside the expression, and prints the expression, so the fix is to move the value rather than reword the expression.
Why can I not use env in runs-on or a job-level if?
Because both are evaluated while the job is still being planned, before a runner exists to hold an environment. The context availability table lists 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?
The workflow syntax reference states it and gives the replacement: "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." An unset secret returns an empty string, which is what makes the environment test reliable.
Where do I check which contexts a workflow key accepts?
The context availability table in the contexts reference. It lists every key that has a restriction, the contexts allowed there, and the special functions allowed there. For each key it lists, the contexts named there are the complete set, and the table states that the listed contexts "may not be used anywhere else", which is the sentence the error comes down to.
Comments
No comments yet. Start the discussion.