Skip to content
Latchkey LogoLatchkey home

GitHub Actions input required and not supplied: token error

The GitHub Actions input required and not supplied: token error comes from @actions/core, which read an environment variable, found it empty, and threw. The word after the colon is the name of the action's input, which is usually not the name of whatever you set.

One secret name becoming an action input name and then an environment variable name
getInput reads process.env with the input name uppercased and prefixed by INPUT_, then throws when the value is empty and the option required is set.

What this error means

The step fails within a second of starting and nothing else in the job is affected. The message names token, so the search begins for a secret called token, and there is rarely one. Steps earlier in the same job that use other secrets work, which makes the whole thing look like a problem with this action specifically rather than with the value it received.

Actions log, quoted from johnm254/openjarvis#1
##[error]Input required and not supplied: token

What the check actually does

The function is four lines. getInput builds an environment variable name by taking the input name, replacing spaces with underscores, uppercasing it and prefixing INPUT_. It reads that variable, defaulting to an empty string if it is absent. Then, if the caller passed required and the value is empty, it throws the message on this page with the input name interpolated into it.

Two things follow. The check cannot distinguish an input that was never provided from one that was provided as an empty string, because both arrive as an empty value. And the name in the message is always the action's input name, chosen by whoever wrote the action, and never the name of your secret or variable.

Common causes

The secret referenced in the with: block does not exist under that name

The common case. A misspelling, a secret created in a different repository, or a name that was changed in settings and not in the workflow all produce an empty interpolation. Nothing warns about referencing a secret that is not there; the expression simply resolves to an empty string.

The job cannot receive secrets on this event

A pull request from a fork gets no repository secrets at all, so every reference empties at once. The workflow is unchanged and correct, and the same step passes on a push. If several secret-dependent steps fail together, this is usually why.

The secret exists at a scope this job cannot read

An organization secret not shared with the repository, or an environment secret in a job that does not name the environment, both resolve to empty. In our experience the environment case is the most persistent, because the secret is plainly visible in settings and the job simply is not in the environment that holds it.

A reusable workflow was called without the secret being passed

The called workflow sees only what the caller forwarded. Without secrets: inherit or an explicit mapping, a step inside it that requires a token gets an empty string, and the failure is reported from a file that never mentions your secret.

How to fix it

Read the with: block before the settings page

  1. Find the failing step and the line whose left-hand side matches the name in the message.
  2. Take the name on the right-hand side, which is yours, and confirm it exists at a scope this job can read.
  3. If there is no such line, the action expected an input it has no default for, and one has to be supplied.

Delete the override when the action has a working default

If the action declares default: ${{ github.token }} for the input, passing a secret that does not exist is strictly worse than passing nothing. Removing the line restores the default and often ends the problem outright.

Name the environment when the secret lives in one

An environment secret is only readable by a job that declares the environment. The declaration belongs on the job, and adding it changes which secrets the job can see.

.github/workflows/publish.yml (illustrative)
jobs:
  publish:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: some-org/publish-action@v2
        with:
          token: ${{ secrets.PUBLISH_TOKEN }}

Forward secrets into a reusable workflow

Pass them explicitly when you want to see what crosses the boundary, or inherit them when the called workflow is yours and the list would be long. The choice is about reviewability rather than correctness.

.github/workflows/release.yml (illustrative)
jobs:
  call-publish:
    uses: ./.github/workflows/publish.yml
    secrets:
      PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}

Three names that are easy to conflate

A value passes through three naming schemes before the check sees it, and the message only ever mentions the middle one.

NameWho chooses itWhether it appears in the message
The secret or variable, such as RELEASE_TOKENYou, in repository or organization settingsNo
The action's input, such as tokenThe action author, in its action.ymlYes, this is the word after the colon
The environment variable, such as INPUT_TOKENThe runner, derived from the input nameNo, but it is what is actually read

Why a default in the action does not always save you

Many actions declare a default for their token input, commonly ${{ github.token }}, and the runner evaluates that before the step starts. With such a default, the input is only empty when you overrode it, which makes an explicit with: line the first thing to examine rather than the last.

That inverts the usual instinct. The step that has no token: line at all is generally the one that works, and the step that carefully passes a secret is the one that fails, because passing an empty secret replaces a working default with nothing. Deleting the line is a legitimate repair and is frequently the right one.

Why there is no recorded run on this page

The check does not touch the network, the filesystem or the runner's state; it reads one environment variable and compares it to the empty string. Recording that would produce a single line identical to the one above, and it would tell you nothing about which of your names, scopes or events left the variable empty, which is the entire question. What can be established once and reused is the transformation from a secret name to an input name to a variable name, and that is fixed in the toolkit's source.

How to prevent it

  • Keep the action's input name and your secret name visibly different, so the message is never mistaken for your name.
  • Let a documented default stand unless a different identity is genuinely needed.
  • Declare the environment on any job that reads environment secrets, at the time you write the job.
  • When a workflow must run on fork pull requests, guard the steps that need secrets rather than letting them fail.

Frequently asked questions

I have no secret called token. Where is that name from?
From the action. getInput interpolates the input name it was asked for, and that name is declared in the action's action.yml. Your own name appears only on the right-hand side of the mapping in the step's with: block, and the check never sees it.
Does the message mean the input was missing or empty?
It cannot tell the difference. The function reads an environment variable and substitutes an empty string when it is absent, then tests only whether the result is empty. A misspelled secret, an unshared one and a deliberately blank value all reach the same branch.
Why do several steps fail with different messages at once?
Because one condition emptied all of them. A fork pull request, an uninherited reusable workflow or an unnamed environment removes every secret in the job together, and each step then reports the absence in its own words. Look for a shared cause before treating them separately.
Is removing the token line really a fix?
When the action declares a default it is, and it is often the correct one. Supplying a secret that resolves to an empty string overwrites a default that would have worked. Check the action's action.yml for a default before adding a secret to solve this.

Related guides

References

An empty input fails the same way on any runner. Latchkey tells configuration and transience apart. Start free → 30-day trial · No credit card