Files
wrkflw/crates/executor
bahdotsh 24c3636075 refactor(executor): route all expressions through the evaluator
The expression evaluator was added in 2954b05, but it only handled
"complex" expressions as a fallback. Simple context references like
${{ env.FOO }} and ${{ runner.os }} were still resolved by six
separate regex preprocessors — each duplicating context resolution
logic that the evaluator already handles perfectly well.

This is the kind of redundancy that makes you maintain the same
mapping in two places and then wonder why they drift apart.

Rip out the individual regex preprocessors (preprocess_env_context,
preprocess_inputs_context, preprocess_github_context,
preprocess_runner_context, preprocess_step_outputs, and five regex
patterns). Now preprocess_expressions does exactly two things:
resolve hashFiles() (needs filesystem access), then route *all*
remaining ${{ }} through the expression evaluator. Net -46 lines.

While at it, fix three issues from code review:

- Add debug logging when expression evaluation fails in the
  substitution path, instead of silently swallowing the error
- Document the surprising Bool/String coercion in expr_eq (where
  false == "random" is true per GitHub Actions semantics)
- Add test coverage for that coercion edge case
2026-04-03 13:03:37 +05:30
..

wrkflw-executor

The execution engine that runs GitHub Actions workflows locally (Docker, Podman, or emulation).

  • Job graph execution with needs ordering and parallel independent jobs
  • Docker/Podman container steps and emulation mode
  • Run individual jobs via target_job / --job flag
  • GitHub Actions environment file support (GITHUB_OUTPUT, GITHUB_ENV, GITHUB_PATH, GITHUB_STEP_SUMMARY) with read-back
  • Docker-based action resolution (container, JavaScript, composite, local)
  • Job-level container: directive support
  • Used by: wrkflw CLI and TUI

API sketch

use wrkflw_executor::{execute_workflow, ExecutionConfig, RuntimeType};

let cfg = ExecutionConfig {
    runtime: RuntimeType::Docker,
    verbose: true,
    preserve_containers_on_failure: false,
    target_job: Some("build".to_string()), // run a single job
};

let workflow_path = std::path::Path::new(".github/workflows/ci.yml");
let result = execute_workflow(workflow_path, cfg).await?;
println!("workflow status: {:?}", result.summary_status);

Prefer using the wrkflw binary for a complete UX across validation, execution, and logs.