Skip to content
Latchkey LogoLatchkey home

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.

Workflow keys against the contexts each one accepts, with env and secrets marked absent
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

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

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 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

  1. Take the workflow key from the line and column in the message, not the context name.
  2. Find that key in the context availability table and read the list of contexts it accepts.
  3. 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.

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 keyContexts availableSpecial functions
envgithub, secrets, inputs, varsNone
jobs.<job_id>.ifgithub, needs, vars, inputsalways, cancelled, success, failure
jobs.<job_id>.runs-ongithub, needs, strategy, matrix, vars, inputsNone
jobs.<job_id>.envgithub, needs, strategy, matrix, vars, secrets, inputsNone
jobs.<job_id>.with.<with_id>github, needs, strategy, matrix, inputs, varsNone
jobs.<job_id>.steps.ifgithub, needs, strategy, matrix, job, runner, env, vars, steps, inputsalways, cancelled, success, failure, hashFiles
jobs.<job_id>.steps.rungithub, needs, strategy, matrix, job, runner, env, vars, secrets, steps, inputshashFiles

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.

How to prevent it

  • Keep the context availability table open while writing any job-level expression.
  • Put configuration in repository or organization variables, where vars is 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?
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. Anything not listed in the table has no restriction, and anything listed "may not be used anywhere else", which is the sentence the whole error comes down to.

Related guides

References

Context availability is decided per key, not per file. Latchkey only changes what the file runs on. Start free → 30-day trial · No credit card