feat: add self-repository action reference syntax - #4457
Merged
ericsciple merged 9 commits intoJul 2, 2026
Conversation
nodeselector
force-pushed
the
nodeselector/automatic-dollop
branch
4 times, most recently
from
July 1, 2026 15:29
129c7b2 to
dc9dab3
Compare
nodeselector
marked this pull request as ready for review
July 1, 2026 17:55
Contributor
There was a problem hiding this comment.
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 sharedPipelineConstants.TryParseDollarSelfReference()helper. - Implemented upfront and runtime resolution of
dollar-selfreferences inActionManagerso they are rewritten into concreteowner/repo/path@shaGitHub 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
ericsciple
reviewed
Jul 2, 2026
ericsciple
reviewed
Jul 2, 2026
ericsciple
reviewed
Jul 2, 2026
ericsciple
reviewed
Jul 2, 2026
nodeselector
force-pushed
the
nodeselector/automatic-dollop
branch
2 times, most recently
from
July 2, 2026 21:13
90b3caf to
87ed816
Compare
ericsciple
approved these changes
Jul 2, 2026
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
force-pushed
the
nodeselector/automatic-dollop
branch
from
July 2, 2026 21:31
87ed816 to
130a893
Compare
1 task
1 task
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.
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.
$/— self-referencing action syntaxAdds
$/pathas a newuses:syntax meaning "this repo, at this SHA, at the given subpath." The containing YAML file determines which repo and SHA — so$/lib/helperinsideacme/toolkit@v1resolves toacme/toolkitat that SHA with pathlib/helper, not the caller's repo.Why
Composite actions that ship multiple sub-actions in one repo currently hardcode
owner/repo@refto 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 (
PipelineTemplateConverterfor action.yml,WorkflowTemplateConverterfor workflow YAML) detect$/viaPipelineConstants.TryParseDollarSelfReference(). The parsed reference getsRepositoryType = "selfRepository"andPath = <subpath>— noNameorRefyet. Leading slashes are stripped and bare$/(no subpath) is rejected.Resolution.
ActionManager.ResolveDollarSelfReferences()rewritesselfRepositoryrefs to concreteGitHub-type refs in-place before they reach any download logic:job.WorkflowRepository/job.WorkflowShaRepositoryPathReference.Name/.Ref$/in a child always means "the repo that contains me."After resolution, the ref is indistinguishable from a normal
owner/repo/path@shareference.Both resolution paths supported. Works on both the batch resolution path (
PrepareActionsRecursiveAsync, gated onactions_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 duringPrepareActionsAsync(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.
LoadActionre-parses action.yml from disk at execution time, producing freshRepositoryPathReferenceobjects withRepositoryType = "selfRepository".ResolveDollarSelfReferencesruns again using the parent's already-resolvedName/Ref. No network calls — the tarball is already cached.Safety net.
GetDownloadInfoLookupKeythrowsInvalidOperationExceptionif aselfRepositoryref reaches it unresolved.Cross-repo
$/context propagationThis works because
childRepoName/childRepoRefat each depth come from the parent action's resolvedRepositoryPathReference.Name/.Ref, not from the workflow-level context.Tests (7 total)
ResolvesAtDepthZero— batch path, top-level$/resolutionResolvesAtDepthZero_LegacyPath— legacy path equivalentNotResolvedWhenFeatureFlagDisabled—$/stays unresolved, hits safety netResolvesNestedInComposite— depth-1$/inside a composite (batch)ResolvesNestedInComposite_LegacyPath— legacy path equivalentCrossRepoCompositeResolvesToParentRepo—$/resolves to parent's repo, not caller'sMultiLevelChain— 3-level composite chain with$/at each depth