Skip to content

feat: run an imported workflow end to end on the host - #83

Merged
Mearman merged 10 commits into
mainfrom
feat/host-run-wiring
Oct 11, 2026
Merged

Mearman merged 10 commits into
mainfrom
feat/host-run-wiring

Conversation

@Mearman

@Mearman Mearman commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Runs an imported native workflow end to end on the host through one command: import then run --local executes a node action, a composite action, a GITHUB_OUTPUT hand-off between steps and an if: gate reading a step output, with no container and no generated file (#76, on top of #77's evaluator, #80's runner file commands, #81's composite resolution and #82's node expansion).

The runner's run options gain a step host port: before a job runs, the host is asked to prepare the job's steps for the combination about to run, and when the run reaches a provider step the preparation left in place, the host resolves it into the commands to run, each of which receives the runner's file commands and its own report entry like a native step. A run without a host is unchanged. The conformance kit covers the port: placement, per-command reports and file commands, the first failure stopping the rest, resolution against what earlier steps produced, the skip and unhandled answers, and that a job which does not run is never prepared.

The CLI supplies that host for GitHub's steps, in the two moments the hosted runner itself works in. Before a job runs it expands every action the job's uses steps name and places the pre and post phases it declared around the job's own steps, the placement the library documents from the runner's own phases; the expanded steps are discarded, because the same action expands again when the step is reached, against what the job's earlier steps by then produced, which is the view GitHub evaluates a with: mapping and a step condition with. A step carrying a run instead resolves directly: its condition decides whether it runs, and its command text, environment and working directory are evaluated as written. The neutral run context maps onto the github context's own names, step outputs and needed-job results reach expressions through the runner's reports, and secrets arrive by --secret name=value or --secret name, which reads the environment variable of that name; neither secret values nor the token are printed, logged or stored. Local ./ references resolve against the workspace, an owner/repo@ref reference is fetched as a tarball from the API with the token re-attached across the one redirect, extracted once per reference by a reader that carries the pax headers a repository tarball actually holds, and removed at the run's end; a node action's entry runs under the CLI's own node.

A wrapper-form call runs one step range of a job while the provider's own runner has already run the steps around it as native steps, so the preparation view carries the range and a host resolves the actions of the steps that will run; this repository's own wrapper-form test job in CI is what caught that.

A docker:// reference and another provider's steps stay provider-owned, skipped by --skip-provider-steps and failing the run without it; the same flag now skips whole actions, for a host that has already supplied the toolchain those steps would install, which is what this repository's own local pipeline run passes it for.

Resolving the real pinned actions/checkout through the fetching resolver showed metadata shapes the node schema wrongly rejected, fixed here: an input default written with no value, which reads as null and means no default; a node action's outputs at the root of its metadata, each a description with no value because the entry sets the value at run time; and the optional author the syntax page documents. The three actions this repository's own pipeline pins all parse against the schema now.

The demonstration is a unit test over the real CLI entry with real ports: a workflow fixture holding a composite action and a node action, imported then run through --local, asserted on its effects on the workspace (files written, the output hand-off reaching a later expression, the gated step skipped when its condition is false, the secret never echoed), a second test feeding a synthetic tarball through the injected fetch port with a pax global header and a long path and asserting the pre/main/post placement, and tests holding the docker, missing-secret and shell rejections to their messages. The integration suite resolves the pinned checkout action over the network through the same resolver and reads its metadata as the node action it is.

Fixes #76

A run options host takes over the provider steps. Before a job runs, the
runner asks it to prepare the job's steps for the combination about to
run: the host places the phases an imported action declares around the
job's own steps and adds job-level environment its provider carries. When
the run reaches a provider step the preparation left in place, the host
resolves it into commands, each of which receives the runner's file
commands and its own report entry like a native step, and the first
failure stops the rest. The resolver sees what the job's earlier steps
produced, both their outputs and the environment they wrote.

A run without a host is unchanged: provider steps are still rejected
upfront, or reported skipped when the run was told to skip them.
The run command now wires a GitHub Actions host into the runner's step
host port. Before each job runs, the host expands every action the job's
uses steps name and places the pre and post phases it declared around the
job's own steps, the placement the library documents from the runner's
own phases. When the run reaches a provider step, the host resolves it
with the view that moment holds: a uses step expands against what the
job's earlier steps produced, so a with mapping or a condition reading a
step output works, and a step carrying a run has its condition, command
text, environment and working directory evaluated as written.

Local ./ references resolve against the workspace. A remote owner/repo
reference is fetched as a tarball from the API, following the one
redirect with the token re-attached, extracted once per reference into a
directory the run removes at its end, and served as a tree. A node
action's entry runs under the node the CLI itself runs under, with its
inputs in the environment as the runner derives them.

The neutral run context maps onto the github context's own names, needs
results and step outputs reach expressions through the runner's reports,
and secrets arrive by --secret name=value or --secret name, which reads
the environment variable and never appears on a command line; the
platform token is supplied as secrets.GITHUB_TOKEN wherever the run has
one. Neither is printed, logged or stored.

A docker:// reference and a step of another provider stay provider
steps: skipped when the run was told to skip provider steps, and
rejected naming them otherwise, as before.

The demonstration is a unit test over the real CLI entry: a workflow
imported then run through --local, using a composite action, a node
action, a GITHUB_OUTPUT hand-off and an if gate, asserted on its effects
on the workspace; a second test feeds a synthetic tarball through the
injected fetch port and asserts the phase placement; docker, secrets and
shell rejections are held to their messages.
…s declare

Resolving the pinned actions/checkout through a host run showed two
shapes its schemas rejected that GitHub's own syntax page carries and
actions in wide use declare: an input's default written with no value,
which reads as null and means no default (mapped as absent, not the text
null), and a node action's outputs at the root of its metadata, each a
description with no value because the entry sets the value at run time
through its output file. A composite action's outputs stay under runs
with their expressions, which is where that form declares them.
Fetches the pinned actions/checkout over the network through the same
resolver a run supplies, extracts the tarball, and reads its metadata as
the node action it is and its entry file. The token goes along when the
environment holds one, so a private clone resolves it too.

The extraction also learned the shape real tarballs carry that the
synthetic feed did not: a directory entry for the repository root
itself, and directory names ending in the slash that ends them.
…wned

The Run directly and Import sections of the README and the runner
discussion in docs/providers.md now describe the two moments a run
resolves GitHub's own steps in, the context expressions evaluate with,
where secrets and the platform token come from, and the constructs that
stay provider-owned unless the run was told to skip them.
@Mearman
Mearman force-pushed the feat/host-run-wiring branch from 7b55288 to 2931d7e Compare October 11, 2026 22:05
A wrapper-form call runs one step range of a job while the provider's
own runner has already run the steps around it as native steps, so
preparing the whole job expanded actions whose steps were outside the
range, fetching and even failing on them. The preparation view now
carries the range, and a host resolves the actions of the steps that
will run, so a step outside the range contributes nothing, not even the
phases its action declares. Caught by this repository's own wrapper-form
test job in CI.
The metadata syntax page documents an optional author, which
actions/setup-node and other published actions carry, so it is read as
data beside the branding rather than rejected. The three actions this
repository's own pipeline pins all parse against the node schema now.
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@Mearman
Mearman marked this pull request as ready for review October 11, 2026 22:25
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-10-11T22:32:22.951391Z f29021e Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@Mearman
Mearman merged commit c4c08a4 into main Oct 11, 2026
12 checks passed
@Mearman
Mearman deleted the feat/host-run-wiring branch October 11, 2026 22:32
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.6.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Run an imported workflow end to end on the host from the CLI

1 participant