# Enforcing Strict Workflow Schema Validation in act: A Complete Technical Guide

> Enforce strict workflow schema validation in act with the --strict flag. Catch unknown keys legacy syntax and invalid expression functions before jobs run. Read this technical guide.

- Repository: [nektos/act](https://github.com/nektos/act)
- Tags: how-to-guide
- Published: 2026-03-03

---

**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`](https://github.com/nektos/act/blob/main/cmd/root.go), the flag is bound to the `Input` struct:

```go
rootCmd.Flags().BoolVar(&input.strict, "strict", false,
    "use strict workflow schema")

```

2. **Planner Initialization** – The boolean flows into the planner constructor:

```go
planner, err := model.NewWorkflowPlanner(
    input.WorkflowsPath(),
    input.noWorkflowRecurse,
    input.strict)

```

3. **Workflow Reading** – In [`pkg/model/workflow.go`](https://github.com/nektos/act/blob/main/pkg/model/workflow.go), the `ReadWorkflow` function selects the appropriate unmarshalling strategy:

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

```go
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`](https://github.com/nektos/act/blob/main/pkg/schema/schema.go) loads JSON schema files ([`workflow_schema.json`](https://github.com/nektos/act/blob/main/workflow_schema.json), [`action_schema.json`](https://github.com/nektos/act/blob/main/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`](https://github.com/nektos/act/blob/main/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:

```bash

# 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`](https://github.com/nektos/act/blob/main/.github/workflows/invalid.yml) with an invalid key:

```yaml
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    foo: bar
    steps:
      - run: echo "Hello"

```

Strict validation produces an immediate error:

```bash
$ 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:

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

```bash
$ 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`](https://github.com/nektos/act/blob/main/pkg/model/workflow.go) and [`pkg/schema/schema.go`](https://github.com/nektos/act/blob/main/pkg/schema/schema.go)
- Enable strict mode by passing `--strict`, which sets `input.strict` in [`cmd/root.go`](https://github.com/nektos/act/blob/main/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`](https://github.com/nektos/act/blob/main/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`](https://github.com/nektos/act/blob/main/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`](https://github.com/nektos/act/blob/main/pkg/schema/schema.go).

### Where does the actual schema validation happen in the codebase?

The validation logic resides in [`pkg/schema/schema.go`](https://github.com/nektos/act/blob/main/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`](https://github.com/nektos/act/blob/main/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.