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-root definition, allowing unknown properties and legacy syntax for backward compatibility
  • Strict: Uses the workflow-root-strict definition, 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:

  1. CLI Flag Declaration – In cmd/root.go, the flag is bound to the Input struct:
rootCmd.Flags().BoolVar(&input.strict, "strict", false,
    "use strict workflow schema")
  1. Planner Initialization – The boolean flows into the planner constructor:
planner, err := model.NewWorkflowPlanner(
    input.WorkflowsPath(),
    input.noWorkflowRecurse,
    input.strict)
  1. Workflow Reading – In pkg/model/workflow.go, the ReadWorkflow function 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: bar inside 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 to success()

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-strict JSON schema definition to enforce GitHub's official workflow specification, implemented in pkg/model/workflow.go and pkg/schema/schema.go
  • Enable strict mode by passing --strict, which sets input.strict in cmd/root.go and propagates through model.NewWorkflowPlanner to the ReadWorkflow function
  • The WorkflowStrict struct triggers schema validation during UnmarshalYAML, utilizing schema.Node.UnmarshalYAML to 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →