Repository navigation
feat: run composite actions from a uses reference on the host - #81
Merged
Merged
Conversation
…etadata parseActionReference reads a uses value into its forms: a local ./path, whose .. segments are kept for the base it appears in to apply, and an owner/repo, owner/repo@ref or subpath reference. Anything else, a docker:// image and the gated $/ alias among them, is rejected naming what the value is. compositeActionSchema validates the metadata of an action whose runs.using is composite: the declared inputs, the run and uses steps with the keys GitHub documents for each, and branding and runs.outputs carried as data. Keys GitHub does not document for a composite action are rejected by the strict objects. Sources: GitHub's metadata syntax documentation and the actions/runner source for what its parser accepts.
expandCompositeAction takes the provider step the importer builds for a uses: reference, resolves the action through an injected port, and returns the run steps and nested provider steps the model runs. Every declared input is mapped from the supplied with or its default, with input names matched without regard to case, and the template expressions of with values, defaults and the action's steps are evaluated through the expression evaluator against a context whose inputs holds the mapped values and whose github.action_path is the directory the host resolved the action to. Each expanded run step carries its own evaluated environment plus GITHUB_ACTION_PATH, and a nested action that is not composite stays a provider step carrying the text as written. Nesting recurses to the ten actions GitHub's own runner allows, and a cycle is rejected naming the whole reference chain. An undeclared input, a required input with no value, a shell or continue-on-error the model has no step form for and metadata that does not match the schema are all rejected naming what and where, the metadata by file and JSON pointer. Refs #74; wiring the expansion into the runner is #76.
Member
Author
|
@codex security review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
🎉 This PR is included in version 0.4.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This was referenced Oct 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds composite-action resolution and interpretation to
@pipewright/pipeline-github-actions: the library half of running auses:step without a container (#71). The runner and CLI wiring is #76; this changes no consumer yet.src/action.tsreads what a reference and a composite action's metadata are.parseActionReferencetakes ausesvalue into its forms: a local./path, whose..segments are kept because only the base it appears in (the workspace for a workflow step, the action's own directory for a composite step) can apply them, andowner/repo,owner/repo@refand the subpath form, split at the first@because a ref may contain one while a locator cannot. Adocker://image and the gated same-repository$/alias are rejected naming what they are.compositeActionSchemavalidates the metadata file GitHub documents: the declared inputs (GitHub gives an action's inputs no type, unlike a reusable workflow's; every value an action receives is a string), the run and uses steps with the keys each form allows (shellrequired besiderun, which is GitHub's own rule), andbrandingandruns.outputscarried as data. A step is validated through the branch its present key picks, so one wrong key is named where it is rather than reported against both branches of a union.Resolution goes through an injected port, never a request of the library's own:
ActionResolvermaps a reference to anActionTree, which exposes the directory the host materialised the action at, a reader for its files relative to its own directory (action.yml, then the documentedaction.yamlalternative), andlocalfor resolving a nested./reference against that directory. A CLI host resolves local references against the workspace and remote ones at their ref, serving the repository being run from the checkout, and tests serve in-memory trees. A fetched tree is data: it is read, and nothing in it executes because it was fetched.expandCompositeActiontakes the provider step the importer already builds for auses:reference, the resolver port and an evaluation context, and returns the expanded neutral steps. Each declared input is mapped from the suppliedwithor its default: names match without regard to case, an optional input that arrived unset reads as the empty string (both are the runner's own behaviour), and defaults evaluate against the calling context, which is the view the runner evaluates them with, before the child's inputs context exists. The action's own steps then evaluate against a context whoseinputsholds the mapped values and whosegithub.action_pathis the host's directory, with each step's own environment merged for its expressions, and each expanded run step carries its evaluated environment plusGITHUB_ACTION_PATH. Two things GitHub tolerates are errors instead, because each means the workflow and the action disagree: a supplied input the action does not declare (GitHub warns and drops it) and a required input with neither a value nor a default (GitHub passes the empty string).Nested
usesrecurse. A nested action that is not composite, and adocker://reference, do not: they stay provider steps carrying theusesandwithtext as written, which is how the importer carries a workflow's ownusessteps, so node actions remain #75's business and docker remains rejected at run time as today. Nesting is bounded at ten actions, derived from the runner's own limit:CompositeActionsMaxDepth = 9in actions/runner'ssrc/Runner.Common/Constants.cs, enforced as a throw once the depth counted from zero at the workflow's steps passes nine, which allows ten; the expansion counts the action the step names as the first of those ten and rejects an eleventh. A cycle is recognised by canonical identity (repository at ref plus normalised directory, so./..climbing out of a nested action back to an ancestor is caught, not just a repeated reference text) and rejected naming the whole chain.Anything the model has no form for is rejected naming what and where, with the metadata file and a JSON pointer into it: malformed metadata, a step with both or neither of
runanduses, a key no composite step has, a shell outsidebashandsh(the neutral run step carries no shell and a host of this tool runs commands through one POSIX shell of its own, so another interpreter's text would be mis-run rather than run), andcontinue-on-error, which would change how a failing step concludes.test/composite.unit.test.tscovers each reference form and its rejections, a local composite action with defaults and expressions, a same-repository reference served from the checkout, a remote action with one level of nesting, nested non-composite and docker steps staying provider steps, the cycle naming the chain, the bound at ten and eleven, and the metadata rejections by file and pointer. The runner, the CLI and the contract package are untouched.Fixes #74