Skip to content

feat: add self-repository action reference syntax - #4457

Merged
ericsciple merged 9 commits into
actions:mainfrom
nodeselector:nodeselector/automatic-dollop
Jul 2, 2026
Merged

feat: add self-repository action reference syntax#4457
ericsciple merged 9 commits into
actions:mainfrom
nodeselector:nodeselector/automatic-dollop

Conversation

@nodeselector

@nodeselector nodeselector commented May 27, 2026

Copy link
Copy Markdown
Contributor

$/ — self-referencing action syntax

Adds $/path as a new uses: syntax meaning "this repo, at this SHA, at the given subpath." The containing YAML file determines which repo and SHA — so $/lib/helper inside acme/toolkit@v1 resolves to acme/toolkit at that SHA with path lib/helper, not the caller's repo.

Why

Composite actions that ship multiple sub-actions in one repo currently hardcode owner/repo@ref to reference siblings. This creates a circular versioning problem — you can't cut a release tag until the YAML already references that tag. $/ breaks the cycle: siblings reference each other by path, and the ref is inherited from whatever version the caller pinned.

Feature flag

actions_dollar_self_reference — gates parsing (Launch → parser), server-side resolution (ARS), and runner-side resolution. Unified name across all four repos.

How it works

Parsing. Both template converters (PipelineTemplateConverter for action.yml, WorkflowTemplateConverter for workflow YAML) detect $/ via PipelineConstants.TryParseDollarSelfReference(). The parsed reference gets RepositoryType = "selfRepository" and Path = <subpath> — no Name or Ref yet. Leading slashes are stripped and bare $/ (no subpath) is rejected.

Resolution. ActionManager.ResolveDollarSelfReferences() rewrites selfRepository refs to concrete GitHub-type refs in-place before they reach any download logic:

Depth Source of truth Why
0 (workflow steps) job.WorkflowRepository / job.WorkflowSha Points to the repo containing the workflow file. Correct for both regular and reusable workflows.
> 0 (inside composites) Parent action's RepositoryPathReference.Name / .Ref The parent is already resolved. $/ in a child always means "the repo that contains me."

After resolution, the ref is indistinguishable from a normal owner/repo/path@sha reference.

Both resolution paths supported. Works on both the batch resolution path (PrepareActionsRecursiveAsync, gated on actions_batch_action_resolution) and the legacy serial path (PrepareActionsRecursiveLegacyAsync). The batch path is not yet broadly rolled out, so legacy support is required.

Upfront resolution (not lazy). All $/ refs are resolved during PrepareActionsAsync (Setup Job phase) before any step executes. Pre/post step registration depends on the complete recursive walk having finished, so lazy resolution would break it.

Runtime re-resolution. LoadAction re-parses action.yml from disk at execution time, producing fresh RepositoryPathReference objects with RepositoryType = "selfRepository". ResolveDollarSelfReferences runs again using the parent's already-resolved Name/Ref. No network calls — the tarball is already cached.

Safety net. GetDownloadInfoLookupKey throws InvalidOperationException if a selfRepository ref reaches it unresolved.

Cross-repo $/ context propagation

private-server-1/actions/crossrepo-chain/action.yml
  └─ uses: github/public-server/actions/leaf@sha
       └─ leaf/action.yml says: uses: $/actions/other-leaf
          → resolves to public-server/actions/other-leaf@sha  ✅
          (NOT private-server-1 — $/ means the repo containing THIS file)

This works because childRepoName/childRepoRef at each depth come from the parent action's resolved RepositoryPathReference.Name/.Ref, not from the workflow-level context.

Tests (7 total)

  • ResolvesAtDepthZero — batch path, top-level $/ resolution
  • ResolvesAtDepthZero_LegacyPath — legacy path equivalent
  • NotResolvedWhenFeatureFlagDisabled$/ stays unresolved, hits safety net
  • ResolvesNestedInComposite — depth-1 $/ inside a composite (batch)
  • ResolvesNestedInComposite_LegacyPath — legacy path equivalent
  • CrossRepoCompositeResolvesToParentRepo$/ resolves to parent's repo, not caller's
  • MultiLevelChain — 3-level composite chain with $/ at each depth

@nodeselector
nodeselector force-pushed the nodeselector/automatic-dollop branch 4 times, most recently from 129c7b2 to dc9dab3 Compare July 1, 2026 15:29
@nodeselector
nodeselector marked this pull request as ready for review July 1, 2026 17:55
@nodeselector
nodeselector requested a review from a team as a code owner July 1, 2026 17:55
Copilot AI review requested due to automatic review settings July 1, 2026 17:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new uses: syntax $/path (“dollar-self”) to allow actions and workflows to reference sub-actions within the same repository at the pinned SHA, eliminating circular versioning for composite actions.

Changes:

  • Added parsing support for $/ in both the workflow parser (WorkflowTemplateConverter) and pipeline template converter (PipelineTemplateConverter) via a shared PipelineConstants.TryParseDollarSelfReference() helper.
  • Implemented upfront and runtime resolution of dollar-self references in ActionManager so they are rewritten into concrete owner/repo/path@sha GitHub refs before download/prepare/load logic.
  • Added L0 tests covering depth-0 resolution, nested composites, cross-repo context propagation, multi-level chains, and legacy (non-batch) resolution.
Show a summary per file
File Description
src/Test/L0/Worker/ActionManagerL0.cs Adds L0 coverage for dollar-self resolution across batch + legacy code paths.
src/Sdk/WorkflowParser/WorkflowConstants.cs Defines DollarSelfAlias for workflow parsing/id generation.
src/Sdk/WorkflowParser/Conversion/WorkflowTemplateConverter.cs Detects $/ uses values when generating default step IDs.
src/Sdk/DTPipelines/Pipelines/PipelineConstants.cs Introduces DollarSelfAlias, DollarSelfPrefix, and TryParseDollarSelfReference().
src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs Parses $/path into RepositoryPathReference with RepositoryType = dollar-self.
src/Runner.Worker/ActionManager.cs Resolves dollar-self refs before batching/downloading and again at load-time for re-parsed composites; adds safety-net throw if unresolved reaches lookup.
src/Runner.Common/Constants.cs Adds actions_dollar_self_reference feature flag constant.

Review details

  • Files reviewed: 7/7 changed files
  • Comments generated: 9
  • Review effort level: Low

Comment thread src/Sdk/DTPipelines/Pipelines/PipelineConstants.cs Outdated
Comment thread src/Runner.Worker/ActionManager.cs Outdated
Comment thread src/Test/L0/Worker/ActionManagerL0.cs Outdated
Comment thread src/Test/L0/Worker/ActionManagerL0.cs Outdated
Comment thread src/Test/L0/Worker/ActionManagerL0.cs Outdated
Comment thread src/Test/L0/Worker/ActionManagerL0.cs Outdated
Comment thread src/Test/L0/Worker/ActionManagerL0.cs Outdated
Comment thread src/Test/L0/Worker/ActionManagerL0.cs Outdated
Comment thread src/Test/L0/Worker/ActionManagerL0.cs Outdated
Comment thread src/Runner.Worker/ActionManager.cs Outdated
Comment thread src/Sdk/WorkflowParser/WorkflowConstants.cs
Comment thread src/Runner.Worker/ActionManager.cs Outdated
Comment thread src/Runner.Worker/ActionManager.cs Outdated
@nodeselector
nodeselector force-pushed the nodeselector/automatic-dollop branch 2 times, most recently from 90b3caf to 87ed816 Compare July 2, 2026 21:13
@ericsciple
ericsciple enabled auto-merge (squash) July 2, 2026 21:29
Add $/path as a uses: syntax meaning "this repo, at this SHA, at the
given subpath." The containing YAML file determines which repo and SHA,
so $/lib/helper inside acme/toolkit@v1 resolves to acme/toolkit at that
pinned SHA — not the caller's repo.

Parsing: PipelineConstants.TryParseDollarSelfReference() shared helper,
used by both PipelineTemplateConverter (action.yml) and
WorkflowTemplateConverter (composite step IDs).

Resolution: ResolveDollarSelfReferences() rewrites dollar-self refs to
concrete GitHub-type refs in-place. At depth 0, uses
WorkflowRepository/WorkflowSha exclusively. At depth > 0, inherits
parent action's resolved Name/Ref. Runs both at Setup Job time
(PrepareActionsRecursiveAsync) and at runtime (LoadAction re-parses
action.yml from disk, producing fresh unresolved refs).

Batch path fix: nested composite sub-steps with $/ must be resolved
BEFORE ResolveNewActionsAsync, otherwise GetDownloadInfoLookupKey throws
on null Name.

Safety net: GetDownloadInfoLookupKey throws InvalidOperationException if
a dollar-self ref reaches it unresolved.

Gated behind actions_dollar_self_reference feature flag.
Run service always uses batch resolution (PrepareActionsRecursiveAsync),
so the legacy path is never hit for $/ features. Remove dead dollar-self
plumbing from PrepareActionsRecursiveLegacyAsync to keep the code honest
about where resolution actually happens.
Batch action resolution (actions_batch_action_resolution) is not broadly
rolled out — it's only enabled for specific self-hosted runners. The
legacy path must also support $/ resolution since most runners will
use it.

Adds dollarSelfRepoName/dollarSelfRepoRef params to the legacy method,
resolves $/ at each depth before download, and propagates parent repo
context on recursive calls. Includes two new legacy-path tests.
Eric noted "dollar" describes syntax, not behavior. Renamed the wire
value and its constant (DollarSelfAlias → SelfRepositoryAlias) across
SDK, worker, and tests. ARS will get the matching rename.
Strip leading slashes from $/ subpath to prevent rooted-path
injection (e.g. "$//foo" → "foo" not "/foo"). Reject bare "$/"
with no subpath. Clarify the unresolved-reference error to cover
flag-off and missing-context cases, not just server version.
Renames internal identifiers, method names, parameters, test names,
and comments across the codebase. Also renames the vexi flag from
actions_dollar_self_reference to actions_self_reference. The wire
value "selfRepository" is unchanged.
Flag const, flag string, method names, test names, and parser
helpers all use SelfRepository now, matching the wire value.
When a dot-slash (./) composite action's action.yml is re-parsed at
runtime by LoadAction, any $/ steps in it need resolution. The parent
action has repositoryType "self" so its Name and Ref are null — the
code passed those nulls to ResolveSelfRepositoryReferences, which
silently returned (empty-name guard). The nested $/ step stayed
unresolved with repositoryType "selfRepository" and no Name/Ref.
When LoadAction then processed that step, repoAction.Name.Replace()
threw NullReferenceException.

This matters because ./ resolves from the workspace checkout, which
can differ from the tarball fetched during setup. The composite on
disk may contain steps that weren't visible in the version the server
planned against.

Fix: fall back to WorkflowRepository/WorkflowSha from job context
when the parent is a dot-slash action. Add a defensive guard in
LoadAction for any unresolved selfRepository refs.
@ericsciple
ericsciple force-pushed the nodeselector/automatic-dollop branch from 87ed816 to 130a893 Compare July 2, 2026 21:31
@ericsciple
ericsciple merged commit dde968b into actions:main Jul 2, 2026
11 checks passed
@nodeselector nodeselector changed the title feat: add dollar-self action reference syntax feat: add self-repository action reference syntax Jul 2, 2026
onsails added a commit to onsails/devenv-cache-action that referenced this pull request Aug 19, 2026
`uses: $/post-snapshot` relies on the self-repository syntax from
actions/runner#4457, which is gated behind the `actions_dollar_self_reference`
feature flag. Runners without the flag reject the template with
"Expected format {org}/{repo}[/path]@ref", so the post step of every job using
cache-eval or cache-nix-eval failed and the eval-cache snapshot was never saved.

Replace it with the explicit, flag-independent
onsails/devenv-cache-action/post-snapshot@<sha> form. `./post-snapshot` is not
an option: a dot-slash reference resolves against the caller's checkout.

Add tests/check-post-snapshot-pin.sh, run by the new post-snapshot-pin CI job,
which rejects the `$/` form, requires a full 40-character SHA, and asserts the
pinned commit's post-snapshot/ tree equals HEAD's. Document the resulting
two-step release in AGENTS.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants