Truss
Heads up: This project is still early. A good chunk of the code was AI-generated and is being actively reviewed, tested, and improved. All tests pass and the benchmarks are reproducible, but treat this as experimental for now. Bug reports and contributions are very welcome as we work toward a stable release.
A fast GitHub Actions workflow validator written in Rust. Truss catches configuration errors, semantic issues, and common mistakes in your CI/CD pipelines before you push — so you spend less time debugging failed runs.
Try it in your browser — no install required.
Why Truss?
It's fast and accurate. We tested Truss, actionlint, yamllint, and yaml-language-server against 271 production workflow files from 7 major open-source projects (pytorch/pytorch, rust-lang/rust, tensorflow/tensorflow, facebook/react, microsoft/TypeScript, kubernetes/kubernetes, actions/checkout):
| Tool | Language | Errors | False Positives | Avg Time | Total Time |
|---|---|---|---|---|---|
| Truss | Rust | 0 | 0 | 1.7 ms | 471 ms |
| actionlint | Go | 0 | 0 | 6.8 ms | 1,840 ms |
| yaml-language-server | TypeScript | 0 | 0 | 101 ms | 27,277 ms |
| yamllint | Python | 0 | n/a (style only) | 123 ms | 33,453 ms |
All tools reported zero errors on 271 real-world files — a clean bill of health across the board. The key differentiator is speed: Truss processes each file in ~1.7ms on average, making it 3.9x faster than actionlint, 58x faster than yaml-language-server, and 71x faster than yamllint.
Measured on an Intel i5-7500T @ 2.70GHz, 8GB RAM, Linux 6.17. Your results may vary. See test-suite/ for how to reproduce.
What It Catches
Truss ships with 41 validation rules that go well beyond syntax checking. It validates job dependencies for circular references, checks that your runs-on labels are real GitHub-hosted runners, flags script injection risks, warns about deprecated workflow commands, verifies matrix strategies, validates cron expressions, and much more.
See the full rule list below or check docs/VALIDATION_RULES.md for the details.
How It Compares
Feature Comparison
| Feature | Truss | actionlint | yamllint | yaml-language-server |
|---|---|---|---|---|
| Language | Rust | Go | Python | TypeScript |
| YAML syntax | tree-sitter | Custom | Yes | JSON Schema |
| GHA semantic validation | 41 rules | Yes | No | Partial (schema) |
| Expression validation | ${{ }} syntax, functions, operators |
Strong type-checking | No | No |
| Runner label checks | 22+ labels | Yes | No | No |
| Matrix validation | Structure + keys | Structure + types | No | No |
| Reusable workflows | Inputs, outputs, secrets | Inputs, outputs, secrets | No | No |
| Job dependency cycles | Yes | Yes | No | No |
| Script injection detection | Yes | Yes (+ shellcheck) | No | No |
| Cron validation | Yes | Yes | No | No |
| Action reference format | Yes (incl. subpaths) | Yes | No | No |
| LSP server | Yes (incremental) | No (requested) | No | Yes |
| VS Code extension | Yes | Yes (2 extensions) | No | Yes |
| Avg time per file | 1.7 ms | 6.8 ms | 123 ms | 101 ms |
Other tools in the ecosystem: zizmor (Rust, security-focused, 24 audit rules), action-validator (Rust, schema-based), ghalint (Go, security policies). These are complementary — they focus on security auditing or schema validation rather than semantic correctness.
Getting Started
Install
# From crates.io (recommended)
# Or download a prebuilt binary from the latest release
# https://github.com/JuanMarchetto/truss/releases
# Or build from source
Basic Usage
# Validate a workflow file
# Validate everything in a directory
# Multiple files at once (parallel processing)
# Glob patterns work too
# Pipe from stdin
|
# Only show errors (skip warnings)
# Machine-readable JSON output
# Quiet mode — just the exit code
# Version info
Exit Codes
| Code | Meaning |
|---|---|
| 0 | All files valid |
| 1 | Validation errors found |
| 2 | Bad arguments or no files given |
| 3 | I/O error (file not found, permission denied) |
VS Code Extension
There's a VS Code extension in editors/vscode/ that gives you real-time diagnostics as you type:
&&
It activates automatically on .github/workflows/*.yml files. See editors/vscode/README.md for setup details.
Other Editors
You can hook up truss-lsp to any editor that supports LSP:
Point your editor's LSP client at this binary for .github/workflows/*.yml files. It supports incremental parsing, so re-validation after edits is near-instant.
Validation Rules
41 rules across 5 categories:
Core & Structural (4 rules)
| Rule | What it does |
|---|---|
| SyntaxRule | YAML syntax validation via tree-sitter |
| NonEmptyRule | Catches empty documents |
| GitHubActionsSchemaRule | Validates basic workflow structure |
| WorkflowTriggerRule | on: trigger config (30+ event types) |
Job-Level (9 rules)
| Rule | What it does |
|---|---|
| JobNameRule | Duplicate names, invalid characters, reserved words |
| JobNeedsRule | Dependency validation, circular dependency detection |
| JobIfExpressionRule | Conditional expression validation |
| JobOutputsRule | Output reference validation |
| JobContainerRule | Container image, ports, services config |
| JobStrategyValidationRule | Strategy structure validation |
| RunsOnRequiredRule | Makes sure every job has runs-on |
| RunnerLabelRule | Validates GitHub-hosted runner labels (22+ labels) |
| ReusableWorkflowCallRule | Reusable workflow path and structure |
Step-Level (11 rules)
| Rule | What it does |
|---|---|
| StepValidationRule | Step structure — must have uses or run (not both) |
| StepNameRule | Step name format validation |
| StepIdUniquenessRule | No duplicate step IDs within a job |
| StepIfExpressionRule | Step conditional expressions |
| StepOutputReferenceRule | steps.X.outputs.Y reference validation |
| StepContinueOnErrorRule | Boolean validation for continue-on-error |
| StepTimeoutRule | Timeout value validation |
| StepShellRule | Shell type validation (bash, pwsh, python, etc.) |
| StepWorkingDirectoryRule | Working directory path validation |
| StepEnvValidationRule | Env var names + reserved GITHUB_ prefix detection |
| ArtifactValidationRule | upload/download-artifact parameter validation |
Workflow-Level (9 rules)
| Rule | What it does |
|---|---|
| WorkflowNameRule | Workflow name validation |
| WorkflowInputsRule | Input types and requirements |
| WorkflowCallInputsRule | Reusable workflow call inputs |
| WorkflowCallSecretsRule | Secret definitions and references |
| WorkflowCallOutputsRule | Workflow call output validation |
| TimeoutRule | Job-level timeout validation |
| PermissionsRule | Permission scope validation (15+ scopes) |
| ConcurrencyRule | Concurrency groups and cancel-in-progress |
| DefaultsValidationRule | Default shell and working directory |
Expression, Reference & Security (8 rules)
| Rule | What it does |
|---|---|
| ExpressionValidationRule | ${{ }} syntax, functions, operators |
| ActionReferenceRule | owner/repo@ref format validation |
| EventPayloadValidationRule | Event fields, filter conflicts, cron ranges, activity types |
| SecretsValidationRule | Secret reference format and naming |
| MatrixStrategyRule | Matrix structure and key validation |
| EnvironmentRule | Environment name format |
| ScriptInjectionRule | Flags untrusted inputs used directly in run: blocks |
| DeprecatedCommandsRule | Warns about ::set-output, ::set-env, etc. |
Performance
Real-World Validation
We cloned 7 major open-source repositories and ran every tool against all 271 workflow files found across pytorch/pytorch (139 files), rust-lang/rust (50), tensorflow/tensorflow (33), facebook/react (24), microsoft/TypeScript (18), actions/checkout (7):
| Tool | Errors | False Positives | Avg Time | Total Time | Speedup |
|---|---|---|---|---|---|
| Truss | 0 | 0 | 1.7 ms | 471 ms | — |
| actionlint | 0 | 0 | 6.8 ms | 1,840 ms | Truss is 3.9x faster |
| yaml-language-server | 0 | 0 | 101 ms | 27,277 ms | Truss is 58x faster |
| yamllint | 0 | n/a | 123 ms | 33,453 ms | Truss is 71x faster |
All four tools report zero errors on these 271 files — the test repos' workflows are well-formed. The differentiator here is pure speed: Truss validates each file in about 1.7ms, while actionlint takes 6.8ms, and the Python/TypeScript-based tools take 101-123ms.
To reproduce: cd test-suite && bash scripts/setup-test-repos.sh && bash scripts/run-full-suite.sh
CLI Benchmarks (Hyperfine)
End-to-end timing of truss validate --quiet with --shell=none, 200 runs:
| Fixture | Mean | Min | Max |
|---|---|---|---|
| Simple (14 lines) | 1.7 ms | 1.4 ms | 4.4 ms |
| Medium (multi-step) | 2.4 ms | 2.1 ms | 5.9 ms |
| Complex dynamic (reusable calls) | 3.7 ms | 3.3 ms | 5.1 ms |
| Complex static (matrices) | 4.9 ms | 4.4 ms | 6.3 ms |
| All 4 files (directory scan) | 5.7 ms | 5.0 ms | 7.8 ms |
Head-to-Head: Truss vs. actionlint vs. yamllint
All tools benchmarked on the same machine (Intel i5-7500T @ 2.70GHz, 8GB RAM, Linux 6.17) with Hyperfine (--shell=none, 200 runs, --warmup 10). This is one particular benchmark on one particular machine — your results may vary.
| Fixture | Truss (Rust) | actionlint (Go) | yamllint (Python) |
|---|---|---|---|
| Simple | 1.7 ms | 2.6 ms | 103 ms |
| Complex dynamic | 3.7 ms | 3.7 ms | 140 ms |
| Complex static | 4.9 ms | 4.0 ms | 153 ms |
On individual fixtures, Truss and actionlint are in the same performance class (single-digit milliseconds). Truss is faster on simple files; actionlint is faster on complex ones. Both are 30-60x faster than yamllint. On larger batches (271 real-world files), Truss averages 1.7ms/file vs. actionlint's 6.8ms/file — a 3.9x advantage that comes from Truss's lower per-file overhead, LTO-optimized binary, and parallel rule execution.
Running Benchmarks
Project Structure
truss/
├── crates/
│ ├── truss-core/ # Validation engine — editor-agnostic, deterministic
│ │ ├── lib.rs # Engine with 41 registered rules
│ │ ├── parser.rs # tree-sitter YAML parser (incremental)
│ │ ├── validation/ # 41 rule implementations
│ │ ├── tests/ # 45 test files, 403 tests
│ │ └── benches/ # Criterion benchmarks
│ ├── truss-cli/ # CLI — parallel processing, globs, stdin, JSON output
│ ├── truss-lsp/ # Language Server Protocol adapter
│ └── truss-wasm/ # WebAssembly bindings
├── editors/
│ └── vscode/ # VS Code extension
├── benchmarks/ # Fixtures and Hyperfine results
├── competitors/ # Comparison scripts for actionlint, yamllint, etc.
├── test-suite/ # Multi-repo comparison testing framework
├── scripts/ # Build, test, and comparison automation
├── docs/ # Architecture, rules, test strategy docs
└── .github/workflows/ # CI pipeline
Building & Testing
# Debug build
# Release build
# Run all 403 tests
# Core tests only
CI
Every push to main and every PR runs:
cargo check --workspacecargo test --workspace(403 tests)cargo clippy --workspace -- -D warningscargo fmt --all -- --check
Architecture
Truss follows a "core first" design. All validation logic lives in truss-core, which is editor-agnostic and fully deterministic. The CLI, LSP server, and WASM crate are thin adapters that wrap the core for different interfaces.
Key ideas:
- Performance is a feature, not an afterthought — everything is benchmarked
- Rules are stateless — each rule gets the parsed tree and source, returns diagnostics
- Results are deterministic — same input always produces the same output
- Incremental parsing — the LSP server only re-parses what changed
More details in docs/ARCHITECTURE.md.
Current Status
What's working:
- 41 validation rules with unique rule IDs, all tested (403 tests across 45 test files)
- Zero false positives on 271 production workflow files from pytorch, rust-lang, tensorflow, react, TypeScript, kubernetes, and checkout
- LSP server with real-time diagnostics and incremental parsing
- VS Code extension
- CLI with parallel file processing, globs, stdin, severity filtering, rule filtering (
--ignore-rules,--only-rules), JSON output .truss.ymlconfiguration file support (ignore paths, enable/disable rules per project)- Sub-6ms validation per file, 3.9x faster than actionlint on real-world batches
- WASM bindings and online playground
- CI pipeline (check, test, clippy, fmt)
Coming next:
- Contextual autocomplete
- Neovim and other editor integrations
Out of scope (for now):
- Azure Pipelines / GitLab CI
- Advanced UI
Documentation
- Architecture — Design principles and guidelines
- Validation Rules — All 41 rules in detail
- Test Strategy — How we test
- Planned Improvements — What's on the roadmap
Contributing
Contributions are welcome! See the Contributing Guide to get started.
License
MIT. See LICENSE-MIT.