# GitHub Actions composite action unexpected input

> GitHub Actions composite action unexpected input is a name comparison against action.yml. Read both errors a composite raises and the shell rule.

Source: https://latchkey.dev/learn/github-actions/gha-composite-action-input-error  
Updated: 2026-09-20

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.

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

## 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)
```

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

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

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

> The same comparison runs for published actions, where the usual cause is a version bump: [GitHub Actions unexpected input valid inputs are](/learn/github-actions/gha-unexpected-inputs-valid-inputs-are).

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

## FAQ

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

## References

- [GitHub Actions: metadata syntax, inputs and runs for composite actions](https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax#runs-for-composite-actions)
- [grafana/install-alloy#2: Required property is missing: shell](https://github.com/grafana/install-alloy/issues/2)
- [actions/toolkit#1124: core.getInput does not work inside another action](https://github.com/actions/toolkit/issues/1124)
- [actions/runner: src/Runner.Worker/ActionRunner.cs, the unexpected-input warning](https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionRunner.cs)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
