Enforcing Strict Workflow Schema Validation in act: A Complete Technical Guide
Pass the --strict flag to act to validate GitHub Actions workflows against the strict JSON schema definition (workflow-root-strict), catching unknown keys, legacy syntax, and invalid expression functions before any job executes.
The nektos/act CLI tool emulates GitHub Actions locally, but by default it uses a lenient schema that permits extra keys and deprecated syntax. Enforcing strict workflow schema validation in act ensures your workflows comply with GitHub's official specification, surfacing malformed YAML during the planning phase rather than at runtime.
How Strict Validation Works in act
The validation system operates in two distinct modes controlled by a single CLI flag. When enabled, the strict pipeline performs schema checks during the YAML unmarshalling phase, preventing invalid workflows from reaching the execution engine.
The Two Validation Modes
act supports two schema definitions compiled into the binary:
- Lenient (default): Uses the
workflow-rootdefinition, allowing unknown properties and legacy syntax for backward compatibility - Strict: Uses the
workflow-root-strictdefinition, which rejects unknown keys, validates expression function signatures, and enforces modern GitHub Actions requirements
The Validation Pipeline
The strict flag propagates through three architectural layers:
- CLI Flag Declaration – In
cmd/root.go, the flag is bound to theInputstruct:
rootCmd.Flags().BoolVar(&input.strict, "strict", false,
"use strict workflow schema")
- Planner Initialization – The boolean flows into the planner constructor:
planner, err := model.NewWorkflowPlanner(
input.WorkflowsPath(),
input.noWorkflowRecurse,
input.strict)
- Workflow Reading – In
pkg/model/workflow.go, theReadWorkflowfunction selects the appropriate unmarshalling strategy:
func ReadWorkflow(in io.Reader, strict bool) (*Workflow, error) {
if strict {
w := new(WorkflowStrict)
err := yaml.NewDecoder(in).Decode(w)
return (*Workflow)(w), err
}
w := new(Workflow)
err := yaml.NewDecoder(in).Decode(w)
return w, err
}
Schema Processing and Expression Validation
Both Workflow and WorkflowStrict implement custom UnmarshalYAML methods. The strict version constructs a schema.Node with the workflow-root-strict definition:
if err := (&schema.Node{
Definition: "workflow-root-strict",
Schema: schema.GetWorkflowSchema(),
}).UnmarshalYAML(node); err != nil {
return errors.Join(err, fmt.Errorf(
"Actions YAML Schema Validation Error detected:\nFor more information, see: https://nektosact.com/usage/schema.html"))
}
The schema package in pkg/schema/schema.go loads JSON schema files (workflow_schema.json, action_schema.json) at compile time. The Node.UnmarshalYAML method recursively walks the YAML tree, validating each key against the schema and parsing ${{ … }} expressions using actionlint to verify permitted functions and variables.
What Strict Validation Actually Checks
The workflow-root-strict definition enables additional constraints not enforced by the default mode:
- Unknown Properties: Rejects unrecognized top-level keys (e.g.,
foo: barinside a job definition) - Legacy Syntax: Disallows deprecated workflow structures that GitHub no longer documents
- Expression Functions: Validates that
if:conditions use only allowed functions with correct signatures, such as rejecting arguments passed tosuccess()
These constraints are exercised by the test suite in pkg/schema/schema_test.go, which validates that strict mode correctly handles complex expression validation scenarios.
Enabling Strict Mode: Practical Examples
Running Workflows with Strict Validation
Invoke act with the --strict flag to enable pre-execution schema validation:
# Default lenient mode - runs even with extra keys
act -W .github/workflows/example.yml
# Strict mode - fails fast on schema violations
act -W .github/workflows/example.yml --strict
Catching Unknown Properties
Create a workflow at .github/workflows/invalid.yml with an invalid key:
on: push
jobs:
build:
runs-on: ubuntu-latest
foo: bar
steps:
- run: echo "Hello"
Strict validation produces an immediate error:
$ act -W .github/workflows/invalid.yml --strict
Actions YAML Schema Validation Error detected:
For more information, see: https://nektosact.com/usage/schema.html
Line: 6 Column 5: Unknown Property foo
Validating Expression Functions
Strict mode validates function signatures in conditional logic:
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "Hello"
if: success('extra')
The success() function accepts no arguments, so strict mode reports:
$ act -W .github/workflows/bad-if.yml --strict
Actions YAML Schema Validation Error detected:
For more information, see: https://nektosact.com/usage/schema.html
Line: 8 Column 12: Too many parameters for success expected <= 0 got 1
Summary
- Strict validation uses the
workflow-root-strictJSON schema definition to enforce GitHub's official workflow specification, implemented inpkg/model/workflow.goandpkg/schema/schema.go - Enable strict mode by passing
--strict, which setsinput.strictincmd/root.goand propagates throughmodel.NewWorkflowPlannerto theReadWorkflowfunction - The
WorkflowStrictstruct triggers schema validation duringUnmarshalYAML, utilizingschema.Node.UnmarshalYAMLto check properties and validate${{ }}expressions via actionlint - Strict mode catches unknown keys, invalid function signatures, and legacy syntax before job execution, preventing cryptic runtime failures
Frequently Asked Questions
How do I enable strict workflow schema validation in act?
Pass the --strict flag when executing act. According to the source code in cmd/root.go, this flag binds to input.strict and propagates through the planner initialization in the same file, eventually reaching the ReadWorkflow function in pkg/model/workflow.go where it selects the WorkflowStrict unmarshalling path.
What is the difference between lenient and strict validation modes?
Lenient mode (default) uses the workflow-root schema and permits unknown properties and legacy syntax for compatibility. Strict mode uses workflow-root-strict, which rejects unrecognized keys, validates that expression functions in if: conditions use correct signatures, and enforces modern GitHub Actions syntax as implemented in the schema.Node.UnmarshalYAML method in pkg/schema/schema.go.
Where does the actual schema validation happen in the codebase?
The validation logic resides in pkg/schema/schema.go, where the Node.UnmarshalYAML method traverses the YAML tree against the JSON schema loaded at compile time. This is invoked from the UnmarshalYAML methods on both Workflow and WorkflowStrict structs in pkg/model/workflow.go, with the strict flag determining which schema definition (workflow-root or workflow-root-strict) is applied.
Why does my workflow pass locally without --strict but fail on GitHub?
GitHub's production runners may tolerate certain legacy syntax or extra keys that the strict schema rejects. Running act with --strict validates against the authoritative schema that GitHub uses for new features, ensuring your workflow complies with the documented specification and preventing deployment issues when GitHub deprecates legacy parsing behaviors.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →