GitHub Actions unable to interpret as boolean: what it really says
GitHub Actions unable to interpret as boolean is a message we can find in no part of the product that could emit it, and searching for it is usually a search for one of three real problems. Two of those raise the same error at different moments, one before the run and one during it, and the third raises nothing at all, which is why it is the one that reaches production.

What this error means
You set a key that takes true or false, and either the run refuses to start with wording you did not expect, or a step that has already failed takes the job down with a second error about the value rather than the type, or everything goes green and behaves as though you had written the opposite. The last shape is the dangerous one: a step guarded by a checkbox input runs when the box is unticked, a continue-on-error you set to a variable swallows a real failure, and nothing anywhere is red. The workflow is valid, the expression evaluates, and the value it produced was a string the whole time.
Error: .github/workflows/test.yml (Line: 24, Col: 28): Unexpected value ''
Error: The step failed and an error occurred when attempting to determine whether to continue on error.
Error: The template is not valid. .github/workflows/test.yml (Line: 24, Col: 28): Unexpected value ''Where the phrase does not come from
We looked for this string in the three places that could produce it and did not find it. The workflow schema and the template reader that validates against it live in actions/runner under src/Sdk/WorkflowParser. The expression parser lives in the same repository under src/Sdk/DTExpressions2. The editor and language server that surface the same errors before you push live in actions/languageservices. Searching all three for "Unable to interpret" returns nothing.
A search that cannot fail is not evidence, so we checked the instrument on the same trees. "Unrecognized named-value" appears in ten files and "Unexpected symbol" in twelve, both of which are messages this parser really emits, so the search does reach the strings it is looking for. A nonsense phrase returned zero, so it can also return zero. Twenty-four issue-search results for the phrase paired with "github actions" were all from Pydantic, TypeDoc and unrelated configuration loaders, none of them a workflow error.
One caveat we cannot close: the service that validates a workflow before a run starts is not public, and it is not impossible that it carries wording the shipped parser does not. What we can say is that nothing in the code that the runner, the language server and the VS Code extension all share produces this sentence, and that every real report we found of a boolean type mismatch quotes one of the two messages below instead.
Common causes
The value came from github.event.inputs rather than inputs
The most common of the three by a distance, and the only one with no error text. The event payload carries every dispatch input as a string, so a false checkbox arrives as "false" and evaluates as true. Code that worked when the input was a string type breaks silently the day someone declares it type: boolean, because the two contexts diverge at exactly that point.
A literal that YAML 1.2 does not call a boolean
Writing yes, on or a quoted 'true' in a key that takes a boolean. The reader hands the schema a string, the schema wanted a boolean, and the template reader reports the value back to you as unexpected. This is caught before the run starts, so it costs a push rather than a deployment.
An expression that returned a string
Interpolating a variable, a step output or a vars value into a boolean key. Everything written to $GITHUB_OUTPUT is a string, and so is every repository variable, so continue-on-error: ${{ steps.probe.outputs.ok }} evaluates to a string token, which the boolean definition for that key refuses as an unexpected value. Because the key is only read after the step has failed, this one surfaces in the middle of a run rather than at validation.
A reusable workflow input typed as a string at one end
An input declared type: boolean in the called workflow but passed from a caller value that is a string, or the reverse. The call boundary is where the declared type is applied, so a mismatch surfaces at the call rather than where the value was written.
How to fix it
Read dispatch inputs through the inputs context
Replace github.event.inputs.<name> with inputs.<name> everywhere in a workflow_dispatch or workflow_call workflow. The two contexts carry the same information, and only the second one keeps a declared boolean as a boolean. This is a search and replace, and it is the whole fix for the silent case.
- name: Deploy
if: inputs.deploy
run: ./scripts/deploy.shCompare, rather than coerce, when the value really is a string
When the value comes from a step output, a repository variable or an environment variable, it is a string and will stay one. Turn it into a boolean with an explicit comparison, which returns a real boolean from the expression engine rather than relying on truthiness. Comparing against the exact string you wrote is also self-documenting for the next reader.
- name: Publish
if: steps.probe.outputs.ok == 'true'
run: ./scripts/publish.shWrite boolean literals in one of the six accepted spellings
- Use
trueorfalse, unquoted, in any key the schema types as a boolean. - Do not write
yes,no,onoroffand expect them to be read as booleans. - Do not quote a boolean literal: quoting it makes it a string.
- If your editor or linter rewrites these, check it is not applying YAML 1.1 rules the Actions parser does not follow.
Declare the type on both sides of a reusable workflow
Give the input type: boolean in the called workflow and pass a real boolean from the caller. Passing a vars value or a step output through with: sends a string across the boundary no matter what the callee declares, so convert it with a comparison in the caller before it crosses.
call:
uses: ./.github/workflows/build.yml
with:
publish: ${{ github.ref == 'refs/heads/main' }}What a boolean key really says when you give it a string
It is one error raised at two different moments, and which you meet depends on whether the bad value was a literal or the result of an expression. A literal is checked by the template reader: when the scalar does not match the boolean definition in the schema, TemplateReader.Validate falls through to m_context.Error(literal, TemplateStrings.UnexpectedValue(literal)), and UnexpectedValue is defined in TemplateStrings.resx as "Unexpected value '{0}'". So continue-on-error: yes is reported as an unexpected value, with the offending token quoted back at you.
An expression takes the other path, and it takes it late. A step continue-on-error is only read once the step has already failed: ApplyContinueOnError in ExecutionContext.cs returns immediately unless the result is a failure, and only then calls EvaluateStepContinueOnError. That method evaluates the expression, turns the result into a token, and validates the token against the schema definition for the key, which is boolean: {} and nothing else. BooleanDefinition.IsMatch returns true only for a BooleanToken, so a result that came back a string, or came back with no value at all, fails the same unexpected-value check a bad literal fails, and context.Errors.Check() throws before the converter under it is ever reached.
That is the error quoted at the top of this page, captured by a reporter whose expression produced no boolean. Two things about it are worth keeping. It carries the same wording as the literal case, so the message alone does not tell you which of the two you have: the moment tells you, because this one arrives mid-run and brings a second line naming what the runner was trying to decide. And it says nothing about booleans or token types, which is worth remembering the next time you meet a message that does.
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.BooleanStepsContext, token, 0, null);
context.Errors.Check();
result = WorkflowTemplateConverter.ConvertToStepContinueOnError(context, token);The BooleanToken message is your editor, not the runner
Search this phrase and a third message turns up: "Unexpected type 'BasicExpressionToken' encountered while reading 'steps item continue-on-error'. The type 'BooleanToken' was expected." It is a real message and it is not the runner's. The runner builds its own description for that key as $"step {WorkflowTemplateConstants.ContinueOnError}", which renders step continue-on-error; the wording with steps item in it appears in no file anywhere in actions/runner. The one place it does appear is the TypeScript workflow parser behind the VS Code extension.
The token name settles it independently. A BasicExpressionToken is an expression that has not been evaluated yet, and the runner never asserts a boolean on one: the parse-time call returns early for any expression token, and the run-time call evaluates before it asserts. Only a static analyzer, reading the file without running it, can hold an unevaluated expression and demand a boolean of it.
That was a bug, not a diagnosis. It was reported as github/vscode-github-actions#33 by someone whose input really was declared type: boolean, and the maintainer named it in one line: the extension was converting the field to a boolean rather than allowing it to contain expressions. It was fixed in 2023 and the call is now guarded, so the workflow in that report runs, and the fourth row of the table below is that workflow. If this message is on your screen today, check whether it came from a run or from the problems panel.
case "continue-on-error":
if (!item.value.isExpression) {
continueOnError = item.value.assertBoolean("steps item continue-on-error").value;
} else {
continueOnError = item.value.assertScalar("steps item continue-on-error");
}Only six spellings are booleans
The YAML reader the workflow parser uses follows the YAML 1.2 core schema rather than the older 1.1 rules most people learned. MatchBoolean in YamlObjectReader.cs accepts exactly true, True, TRUE, false, False and FALSE and returns false for everything else, with a comment in the source naming the spec it is following. So yes, no, on, off, y, n, 1 and 0 are all plain strings here, whatever your editor colors them.
That matters twice over. A quoted 'true' is a string too, and so is anything an expression produced unless the expression genuinely returned a boolean. The table below sets the five values side by side, in the order you are most likely to meet them.
| What you wrote | How the parser reads it | What happens |
|---|---|---|
continue-on-error: true | a boolean | works |
continue-on-error: yes | the string "yes" | rejected as an unexpected value |
continue-on-error: 'true' | the string "true" | rejected as an unexpected value |
continue-on-error: ${{ inputs.flag }} with type: boolean | a boolean | works |
if: ${{ github.event.inputs.flag }} | the string "false" or "true" | always truthy, never an error |
The silent one, and the context that fixes it
The last row is the case that costs real time, because there is no message to search for. A workflow_dispatch input declared type: boolean arrives in two places at once, and only one of them keeps the type. The documentation is precise about it: "The information in the inputs context and github.event.inputs context is identical except that the inputs context preserves Boolean values as Booleans instead of converting them to strings."
A string of "false" is not falsy in this expression language. EvaluationResult.IsFalsy in the runner treats a string as false only when it equals the empty string, so "false" is truthy and the step you guarded runs. This was reported as actions/runner#1483, "Boolean inputs are not actually booleans", against a workflow that reads github.event.inputs.foo in a step condition; the inputs context is the answer to it.
on:
workflow_dispatch:
inputs:
deploy:
type: boolean
default: false
jobs:
ship:
if: inputs.deploy
runs-on: ubuntu-latest
steps:
- run: ./scripts/deploy.shWhy there is no recorded run on this page
The main claim on this page is a negative, and a run cannot demonstrate a negative. No number of green jobs would show that a message is never emitted; only reading every branch that could emit one does that, which is what the section above describes and what the code quotation supports. The two real errors are raised before a job exists, so there would be no runner log to capture even if we wanted one. The third case produces a green run whose log is indistinguishable from a correct run, which is exactly why it is worth a page.
How to prevent it
- Treat
github.event.inputsas string-only and reach for theinputscontext instead. - Assume anything written to
$GITHUB_OUTPUTor stored invarsis a string. - Compare with
==when you need a boolean out of a value you did not declare. - Run actionlint in CI: it knows which keys are typed and which contexts are available.
Frequently asked questions
Does GitHub Actions really emit "Unable to interpret as boolean"?
Why does my if condition run when a boolean input is false?
github.event.inputs, which converts booleans to strings. The expression engine treats a string as false only when it is empty, so the string "false" is truthy. Read inputs.<name> instead, which preserves the declared boolean type.Is yes the same as true in a workflow file?
yes, no, on and off are read as ordinary strings, so a key that requires a boolean rejects them.How do I pass a boolean into continue-on-error?
true or false, or use an expression that genuinely returns a boolean, such as a comparison or a workflow_dispatch input declared type: boolean read through the inputs context. An expression that yields a string is refused as an unexpected value, and since the key is read only after a step has failed, you find that out at the worst possible moment.Related guides
References
- GitHub Actions: workflow syntax, on.workflow_dispatch.inputs and the Boolean note
- actions/runner#2418: the unexpected-value error, captured mid-run
- actions/runner#1483: boolean inputs are not actually booleans
- github/vscode-github-actions#33: the BooleanToken message, quoted
- actions/runner: YamlObjectReader.cs, the six spellings that are booleans
- GitHub Actions documentation