Gokul b711e871f7 fix(executor): propagate composite action outputs back to caller (#94)
* fix(executor): propagate composite action outputs back to caller

It turns out that execute_composite_action() was happily running all
the internal steps of a composite action, correctly tracking their
outputs in composite_step_outputs, and then... just throwing all of
that away. The action.yml `outputs:` section — the whole reason
composite actions *have* a return path — was never read or evaluated.

So ${{ steps.my-composite.outputs.whatever }} always resolved to
empty string. Inputs worked fine. The internal steps ran fine. The
output values were right there in memory. Nobody bothered to connect
the last wire.

Add propagate_composite_outputs() which reads the action's outputs
section after the step loop, evaluates each value expression against
the composite's internal step context, and writes the results to the
caller's GITHUB_OUTPUT file. The existing apply_step_environment_updates
pipeline then picks them up naturally — no changes to StepResult or
process_outcome needed.

Also wire this into the early-return failure path so partial outputs
are still available when a composite step fails.

* fix(executor): pass actual job status to composite output propagation

The previous commit (1119f63) added propagate_composite_outputs()
to resolve composite action outputs and write them to the caller's
GITHUB_OUTPUT file. Good fix, but it hardcoded job_status to
"success" in the ExpressionContext.

It turns out that when a composite step *fails*, record_step_status
has already flipped composite_job_status to "failure" — but the
output propagation on the failure short-circuit path was still
cheerfully telling the expression evaluator everything was fine.
Any output expression using success() or failure() builtins would
evaluate with the wrong answer.

Pass the actual composite_job_status to propagate_composite_outputs
instead of lying about it. While at it, add tests for the failure
path (partial outputs from steps that ran before the failure) and
for referencing steps that never existed (should resolve to empty,
not panic).

* fix(executor): handle multiline values in composite output propagation

It turns out that propagate_composite_outputs was writing *all* values
using simple key=value format, which silently corrupts the
GITHUB_OUTPUT file when a resolved expression contains newlines. The
second line onward becomes unparseable garbage, and downstream steps
quietly get empty outputs.

This is not great, especially since parse_github_kv_file already
supports the heredoc format (key<<EOF\nvalue\nEOF) on the read side.
The writer just never bothered to emit it.

Switch to heredoc format when the value contains '\n', and log a
debug diagnostic when the GITHUB_OUTPUT file can't be opened instead
of silently swallowing the error. Add a round-trip test that verifies
multiline values survive the write-then-parse cycle.

* fix(executor): use unique heredoc delimiter and handle write errors in composite output propagation

The heredoc format for multiline composite outputs was using a
hardcoded "EOF" delimiter. If an output value happened to contain
a line that was literally "EOF", parse_github_kv_file would
prematurely terminate the heredoc and silently truncate the value.
This is not great.

On top of that, every write!() call was discarding its Result with
`let _ = ...`, so a partial I/O failure would produce a corrupt
GITHUB_OUTPUT file that the caller would happily misparse.

Replace the hardcoded delimiter with generate_heredoc_delimiter(),
which starts with "ghadelimiter" and appends _1, _2, etc. until
it finds one that doesn't collide with any line in the value. This
matches what real GitHub Actions does (they use UUIDs, but the
principle is the same). Chain the writes with and_then() so the
first failure logs a debug message and breaks out of the loop.

While at it, add tests for delimiter collision and the delimiter
generator itself.

* .gitignore
2026-04-13 15:06:43 +05:30
2025-08-28 12:56:05 +05:30
2025-04-30 17:57:29 +05:30
2025-04-14 17:00:23 +05:30
2025-03-29 12:58:02 +05:30
2025-08-13 17:57:44 +05:30

WRKFLW

Crates.io License Build Status Downloads

A command-line tool for validating and executing GitHub Actions workflows locally. Test your workflows on your machine before pushing to GitHub.

WRKFLW Demo

Features

  • TUI interface — interactive terminal UI for browsing, running, and monitoring workflows
  • Workflow validation — syntax checks, structural validation, and composite action input cross-checking with CI/CD-friendly exit codes
  • Local execution — run workflows using Docker, Podman, or emulation mode (no containers)
  • Job selection — run individual jobs with --job flag or via TUI job selection mode
  • Job dependency resolution — automatic ordering based on needs with parallel execution of independent jobs
  • Action support — Docker container actions, JavaScript actions, composite actions, and local actions
  • Reusable workflows — execute caller jobs via jobs.<id>.uses (local or owner/repo/path@ref)
  • GitHub context emulation — environment variables, GITHUB_OUTPUT, GITHUB_ENV, GITHUB_PATH, GITHUB_STEP_SUMMARY
  • Matrix builds — full support for include, exclude, max-parallel, and fail-fast
  • Secrets management — multiple providers (env, file, Vault, AWS, Azure, GCP) with masking and encryption
  • Remote triggering — trigger workflow_dispatch runs on GitHub or GitLab pipelines
  • GitLab support — validate and trigger GitLab CI pipelines

Installation

cargo install wrkflw

Or build from source:

git clone https://github.com/bahdotsh/wrkflw.git
cd wrkflw
cargo build --release

Quick Start

# Launch the TUI (auto-detects .github/workflows)
wrkflw

# Validate workflows
wrkflw validate

# Run a workflow
wrkflw run .github/workflows/ci.yml

Usage

Validation

# Validate all workflows in .github/workflows
wrkflw validate

# Validate specific files or directories
wrkflw validate path/to/workflow.yml
wrkflw validate path/to/workflows/

# Validate multiple paths
wrkflw validate flow-1.yml flow-2.yml path/to/workflows/

# GitLab pipelines
wrkflw validate .gitlab-ci.yml --gitlab

# Verbose output
wrkflw validate --verbose path/to/workflow.yml

Exit codes: 0 = all valid, 1 = validation failures, 2 = usage error. Use --no-exit-code to disable.

Execution

# Run with Docker (default)
wrkflw run .github/workflows/ci.yml

# Run with Podman
wrkflw run --runtime podman .github/workflows/ci.yml

# Run in emulation mode (no containers)
wrkflw run --runtime emulation .github/workflows/ci.yml

# Run a specific job
wrkflw run --job build .github/workflows/ci.yml

# List jobs in a workflow
wrkflw run --jobs .github/workflows/ci.yml

# Preserve failed containers for debugging
wrkflw run --preserve-containers-on-failure .github/workflows/ci.yml

TUI

# Open TUI with default directory
wrkflw tui

# Open with specific runtime
wrkflw tui --runtime podman

Controls:

Key Action
Tab / 1-4 Switch tabs (Workflows, Execution, Logs, Help)
Up/Down or j/k Navigate
Space Toggle selection
Enter Run / View details
r Run selected workflows
a / n Select all / Deselect all
e Cycle runtime (Docker / Podman / Emulation)
v Toggle Execution / Validation mode
t Trigger remote workflow
q / Esc Quit / Back

Remote Triggering

Trigger workflow_dispatch events on GitHub or GitLab.

# GitHub (requires GITHUB_TOKEN env var)
wrkflw trigger workflow-name --branch main --input key=value

# GitLab (requires GITLAB_TOKEN env var)
wrkflw trigger-gitlab --branch main --variable key=value

Runtime Modes

Mode Description Best for
Docker (default) Full container isolation, closest to GitHub runners Production, CI/CD
Podman Rootless containers, no daemon required Security-conscious environments
Emulation Runs directly on host, no containers needed Quick local testing

Reusable Workflows

jobs:
  call-local:
    uses: ./.github/workflows/shared.yml

  call-remote:
    uses: my-org/my-repo/.github/workflows/shared.yml@v1
    with:
      foo: bar
    secrets:
      token: ${{ secrets.MY_TOKEN }}
  • Local refs resolve relative to the working directory
  • Remote refs are shallow-cloned at the specified @ref
  • with: entries become INPUT_<KEY> env vars; secrets: become SECRET_<KEY>

Limitations: outputs from called workflows are not propagated back; secrets: inherit is not supported; private repos for remote uses: are not yet supported.

Secrets Management

WRKFLW supports GitHub Actions-compatible ${{ secrets.* }} syntax with multiple providers:

# Environment variables (simplest)
export GITHUB_TOKEN="ghp_..."
wrkflw run .github/workflows/ci.yml

# File-based secrets (JSON, YAML, or .env format)
# Configure in ~/.wrkflw/secrets.yml

Supported providers: environment variables, file-based, HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, Google Cloud Secret Manager. See the secrets demo for detailed examples.

Limitations

Supported

  • Workflow syntax validation with exit codes
  • Job dependency resolution and parallel execution
  • Matrix builds, environment variables, GitHub context
  • Container, JavaScript, composite, and local actions
  • Reusable workflows (caller jobs)
  • Environment files (GITHUB_OUTPUT, GITHUB_ENV, GITHUB_PATH, GITHUB_STEP_SUMMARY)
  • TUI and CLI interfaces
  • Container cleanup (even on Ctrl+C)

Not Supported

  • GitHub encrypted secrets and fine-grained permissions
  • actions/cache (no persistent cache between runs)
  • Artifact upload/download between jobs
  • Event triggers other than workflow_dispatch
  • Windows and macOS runners
  • Job/step timeouts, concurrency, and cancellation
  • Service containers in emulation mode
  • Reusable workflow output propagation (needs.<id>.outputs.*)

Project Structure

WRKFLW is organized as a Cargo workspace with focused crates:

Crate Purpose
wrkflw CLI binary and library entry point
wrkflw-executor Workflow execution engine
wrkflw-parser Workflow file parsing and schema validation
wrkflw-evaluator Structural evaluation of workflow files
wrkflw-validators Validation rules for jobs, steps, triggers
wrkflw-runtime Container and emulation runtime abstractions
wrkflw-ui Terminal user interface
wrkflw-models Shared data structures
wrkflw-matrix Matrix expansion utilities
wrkflw-secrets Secrets management with multiple providers
wrkflw-github GitHub API integration
wrkflw-gitlab GitLab API integration
wrkflw-logging In-memory logging for TUI/CLI
wrkflw-utils Shared helpers

License

MIT License - see LICENSE for details.

Description
Validate and Run GitHub Actions locally.
Readme MIT 29 MiB
Languages
Rust 98.6%
Shell 1.4%