Skip to content
Latchkey LogoLatchkey home

GitHub Actions composite action unexpected input

GitHub Actions composite action unexpected input means the runner compared the keys you passed against the inputs the action declares and found one that is not in the list. It is a warning rather than an error, so the step keeps going with the value missing, which is why it usually surfaces as behavior rather than as a red job.

The two checks a composite action fails, one at load time and one at input time
Two separate checks on two separate files. The metadata is validated first and fails the job; the input names are compared second and only warn.

What this error means

Two different failures wear this label and they are worth separating on sight. The first is a warning: the step runs, the job is green, and one option you thought you set had no effect, with a line in the log naming the key you passed and listing the ones the action accepts. The second is fatal and arrives before anything runs: the action fails to load, and the message names a file, a line and a column inside the action rather than inside your workflow. Both are decided by reading the action metadata.

Actions log, quoted from grafana/install-alloy#2
Error: grafana/install-alloy/main/action.yml (Line: 13, Col: 7): Required property is missing: shell
Error: GitHub.DistributedTask.ObjectTemplating.TemplateValidationException: The template is not valid. grafana/install-alloy/main/action.yml (Line: 13, Col: 7): Required property is missing: shell
   [three stack frames omitted]
Error: Failed to load grafana/install-alloy/main/action.yml

A minimal action and caller that produce it

These files are written for this page and have never been run. The action declares one input and forgets the shell on its run step; the caller passes a key the action does not declare. That is one fatal error and one warning in fifteen lines.

action.yml and its caller (illustrative)
# .github/actions/setup/action.yml
name: setup
inputs:
  version:
    description: Toolchain version
    required: true
runs:
  using: composite
  steps:
    - run: ./install.sh "${{ inputs.version }}"

# .github/workflows/ci.yml
      - uses: ./.github/actions/setup
        with:
          version: "1.4.0"
          cache: true

Common causes

The input is not declared in the action metadata

The ordinary case, and usually a spelling or a separator: an underscore where the action uses a hyphen, a plural where the action is singular. The identifier rules allow both spellings, so nothing about the name is invalid, it is just not the one the action published.

A composite run step has no shell

The step key that is optional everywhere else and required here. It is easy to lose when a run step is copied out of a workflow into an action, because in a workflow the job default supplies it. The action then fails to load for every caller at once, which is why this often arrives as a report from a consumer.

The action is read from a different ref than you think

The valid list is built from the metadata at the ref you pinned. A floating major tag that moved, or a local action on a branch you did not check out, gives a list that does not match the documentation you were reading. Pinning to a SHA removes the question.

The value is read through the toolkit rather than the context

Inside a composite action the input environment variables are not set, so a nested script action asking the toolkit for an input gets an empty string. Nothing warns, because from the runner's point of view the input was accepted and passed on as declared.

How to fix it

Read the file position in the message first

  1. If the path in the error is an action.yml, the metadata is the problem and the job will not start.
  2. If the message is a warning naming a key and a valid list, the metadata loaded and only the name is wrong.
  3. If neither appears and the value is empty inside the action, it is declared but read the wrong way.

Put a shell on every composite run step

There is no job default inside an action, so each run step carries its own. Anything on the documented shell list works; bash is the portable default on the hosted Linux and macOS images and is available on the Windows ones.

.github/actions/setup/action.yml (illustrative)
runs:
  using: composite
  steps:
    - run: ./install.sh
      shell: bash

Declare the input, then read it through the inputs context

Declaring it makes the key expected; reading it through the context is what makes the value arrive. Give optional inputs a default so the action behaves predictably when the caller says nothing, and remember that every default is a string.

.github/actions/setup/action.yml (illustrative)
inputs:
  cache:
    description: Restore the toolchain cache
    default: "false"
runs:
  using: composite
  steps:
    - run: echo "cache=${{ inputs.cache }}"
      shell: bash

Forward inputs explicitly to nested actions

A composite action that wraps another action should pass values across rather than assume the inner action can see them. This is the one that turns a silent empty string into working code, and it also documents what the wrapper is actually passing on.

.github/actions/setup/action.yml (illustrative)
    - uses: actions/github-script@v7
      env:
        MY_INPUT: ${{ inputs.my_input }}
      with:
        script: console.log(process.env.MY_INPUT)

The shell key is required, and the check is on the metadata

The metadata syntax reference is exact about the conditional requirement: for a composite step, shell is "Optional The shell where you want to run the command. You can use any of the shells listed in jobs.<job_id>.steps[*].shell. Required if run is set." The runner's action schema says the same without the prose, marking both run and shell required on a run step.

When one is missing the runner refuses to load the action rather than warning. The message comes from the template reader, which builds it from the format string below for every required property it cannot find, and the position in it is inside the action metadata rather than inside your workflow. That is the fastest way to tell this from a workflow syntax error: the path in the error is an action.yml.

actions/runner, TemplateReader.cs in Sdk/DTObjectTemplating
m_context.Error(mapping, $"Required property is missing: {property.Key}");

The corrected pair

The file below is the action above with both defects removed. It gains the shell on its run step, which makes it loadable, and a declaration for the second input, which makes the caller's key expected rather than unexpected. Read them as a pair: the first fails to load and then warns, the second runs clean.

Declaring the input is only half of accepting it. An input that is declared but never read is still silently ignored, so the step that consumes it is part of the fix rather than an optional extra.

action.yml, corrected (illustrative)
# .github/actions/setup/action.yml
name: setup
inputs:
  version:
    description: Toolchain version
    required: true
  cache:
    description: Restore the toolchain cache
    default: "false"
runs:
  using: composite
  steps:
    - run: ./install.sh "${{ inputs.version }}"
      shell: bash
    - if: inputs.cache == 'true'
      run: ./restore-cache.sh
      shell: bash

Why the input comparison only warns

The runner builds the set of valid inputs from the action metadata, subtracts it from the keys you wrote, and reports what is left. The message is built as Unexpected input(s) then '{joined unexpected}', then valid inputs are ['{joined valid}'], so the bracketed list is the complete set rather than a suggestion, read from the action.yml of the exact ref you pinned.

It warns rather than fails because some actions forward keys their metadata cannot enumerate, and the runner cannot tell those from a typo. So a misspelled input survives code review, survives CI, and changes nothing except the one behavior you were configuring.

A third failure belongs here because it looks like an input problem from the inside. A composite action does not get the environment variables a JavaScript or container action gets: "If the action is written using a composite, then it will not automatically get INPUT_<VARIABLE_NAME>. With composite actions you can use the inputs context to access action inputs." actions/toolkit#1124 is the long-running report, where a nested script action read an input through the toolkit and got nothing while the same value interpolated fine through the context.

What is wrongWhere it is detectedSeverityWhat the message names
shell missing on a run steploading action.ymlfails the joba line in action.yml
run and uses both missingloading action.ymlfails the joba line in action.yml
a with: key the action does not declarestarting the stepwarning onlythe key and the valid list
reading an input with the toolkitat runtimesilentnothing
a declared input nothing readsneversilentnothing

Why there is no recorded run on this page

Both halves are decided by reading two YAML files and comparing two sets of names. The result is identical on every runner, on every attempt, so there is nothing transient to retry and nothing for a runner to repair. The error at the top is quoted from grafana/install-alloy#2, where a published action shipped a composite step without a shell and every caller failed to load it; the files above are illustrative.

How to prevent it

  • Treat the inputs block as the action interface and change it in the same commit as the step that reads it.
  • Put a shell on every composite run step as you write it, because there is no job default to fall back on.
  • Pin actions to a SHA so the valid input list is the one you read rather than the one a tag moved to.
  • Run a workflow linter that parses action metadata as well as workflow YAML, so a missing shell never reaches a consumer.

Frequently asked questions

Why does my composite action say Unexpected input?
Because a key in the caller's with: block is not in the action's inputs. The runner builds the valid set from the metadata and names everything left over. The bracketed list is complete, so if the input you wanted is in it with a different spelling, the fix is the spelling.
What does "Required property is missing: shell" mean?
That a composite step sets run without setting shell. The metadata reference marks the key "Required if run is set", and the runner refuses to load the action rather than warning. The line and column point into the action.yml, not into your workflow.
Why are my inputs empty inside a composite action?
Because composite actions do not get the input environment variables. The metadata reference says it directly: a composite action "will not automatically get INPUT_<VARIABLE_NAME>" and you use the inputs context instead. actions/toolkit#1124 is the report where a nested script action read an input through the toolkit and got nothing.
Why does a misspelled composite action input change nothing?
Because the key is dropped rather than rejected. Inside the action inputs.<name> then falls back to the default in action.yml, or to an empty string where there is no default, so the action quietly does its default thing. The only trace is the warning, which is why these survive review and surface later as behavior.

Related guides

References

An action that will not load is red in every repo that calls it. Latchkey runs the retries at $0.0025/min. Start free → 30-day trial · No credit card