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.

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.
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.ymlA 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.
# .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: trueCommon 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
- If the path in the error is an
action.yml, the metadata is the problem and the job will not start. - If the message is a warning naming a key and a valid list, the metadata loaded and only the name is wrong.
- 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.
runs:
using: composite
steps:
- run: ./install.sh
shell: bashDeclare 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.
inputs:
cache:
description: Restore the toolchain cache
default: "false"
runs:
using: composite
steps:
- run: echo "cache=${{ inputs.cache }}"
shell: bashForward 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.
- 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.
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.
# .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: bashWhy 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 wrong | Where it is detected | Severity | What the message names |
|---|---|---|---|
shell missing on a run step | loading action.yml | fails the job | a line in action.yml |
run and uses both missing | loading action.yml | fails the job | a line in action.yml |
a with: key the action does not declare | starting the step | warning only | the key and the valid list |
| reading an input with the toolkit | at runtime | silent | nothing |
| a declared input nothing reads | never | silent | nothing |
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?
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?
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?
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?
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
- GitHub Actions: metadata syntax, inputs and runs for composite actions
- grafana/install-alloy#2: Required property is missing: shell
- actions/toolkit#1124: core.getInput does not work inside another action
- actions/runner: src/Runner.Worker/ActionRunner.cs, the unexpected-input warning
- GitHub Actions documentation