Support self-referencing `$/` uses: syntax in the pin tool by nodeselector · Pull Request #84 · github/gh-actions-lock · GitHub
Skip to content

Support self-referencing $/ uses: syntax in the pin tool - #84

Merged
nodeselector merged 17 commits into
mainfrom
nodeselector-dollar-slash-self-reference
Jul 24, 2026
Merged

nodeselector merged 17 commits into
mainfrom
nodeselector-dollar-slash-self-reference

Conversation

@nodeselector

@nodeselector nodeselector commented Jul 10, 2026 •

Copy link
Copy Markdown
Collaborator

Implements $/ self-referencing uses: support in the pin/lockfile tool.

`uses: $/…` references an action or reusable workflow in the same repo as
the defining file, resolved at the running commit. It is inherently pinned
— parallel to the existing `./…` local treatment — so the tool records no
lockfile entry, never flags it unpinned, and passes it through untouched.
A trailing `@ref` is invalid (a self-reference always resolves to the
running ref) and is rejected.

Detection lives in internal/workflowfile (IsSelfRepoAction /
SelfRepoRefHasVersion); no change to the shared actions-lockfile/go parser
is required, since the CLI classifies `$/` before calling ParseActionRef,
mirroring how `./` is handled. ExtractActionRefs now returns a RefScan that
splits `$/` values into valid self-repo refs and the malformed `$/…@ref`
form. Two new finding categories carry this through the pipeline:
self-repo-action (benign, info) and invalid-self-repo-ref (error). The
offline --verify-local path also rejects `$/…@ref`.

Nested `$/` inside a fetched composite resolves as a same-tarball
self-reference (parent repo at its own ref): still traversed for cross-repo
transitive deps, but records no new pin or edge.

Sub-issue github/actions-dispatch#800.
`--migrate-local-actions` rewrites same-repo `uses: ./…` composite action
references to the `uses: $/…` self-reference form. Only `./…` paths that
resolve to an in-repo action file (action.yml/action.yaml) are rewritten,
which is the equivalence guard that makes `$/` a safe drop-in for `./`;
reusable-workflow calls are never candidates. The flag is skipped under
--no-fix and also works alongside --verify-local. Existing local-action
findings now point at the flag as a remediation hint.

Kept separate from core `$/` support so it can be dropped for a leaner PR.
Adds three offline (--verify-local) scenarios to the shared catalog.yml so
both the Go scenario loader and the Ruby integration harness cover `$/`:

- self_repo_action_valid: bare `$/` at step level verifies clean (inherently
  pinned, no lockfile entry required).
- self_repo_reusable_workflow_valid: `$/` at reusable-workflow call level
  (jobs.<id>.uses) verifies clean.
- self_repo_ref_rejected: `$/…@ref` exits 1 with invalid-self-repo-ref.

Covers core `$/` support; independent of the --migrate-local-actions commit.
Adds two layers of coverage for `$/` self-references nested several levels
deep inside fetched composites, where the transitive closure must still be
walked and pinned:

- Unit (resolver): TestResolveAllRecursiveSelfRepoDeepMultiLevel drives a
  four-level `$/` chain in one repo, a shared `$/` child reached via two
  parents (dedup), a `$/` nested inside a *different* repo (proving `$/`
  binds to the enclosing tarball, not the entry repo), and a diamond back
  to an already-seen cross-repo tarball. Asserts the exact 5-tarball closure,
  no self-parent edges on collapsed `$/` siblings, and precise parent sets.

- Integration (catalog): self_repo_deep_transitive is a real_repo scenario
  against nodeselector/actions-test-fixtures. Its deep-self-ref workflow
  pulls a remote composite whose internals chain `$/` three levels deep; the
  tool fetches the tarball, walks the chain, and pins the cross-repo
  transitive dep (actions-test-fixtures-b) reached through it.

The fixtures (deep-self-ref/{,-b,-c} + the workflow) were added to
nodeselector/actions-test-fixtures@main separately.
Previously the migration only rewrote `./…` -> `$/…` in workflow files, so
a composite action that internally used `uses: ./helper` was left on the
unsupported `./` form. It now also sweeps every in-repo composite action
definition (action.yml/action.yaml under the repo root, `.git` skipped) and
rewrites their local steps too, including ones nested several directories
deep.

The equivalence guard is unchanged: a `./…` is only rewritten when it
resolves to an in-repo action file, so unresolvable or out-of-repo paths are
left untouched. This does not change the hard limit that a `./…` inside a
*remote* fetched composite cannot be onboarded (CompositeLocalPathError) —
that is not our repo to rewrite.

Adds DiscoverCompositeActionFiles plus unit coverage for the workflow +
composite sweep and the discovery walk.
Adds a deep variant of the composite-local-path limit: a $/ self-reference
chain several levels deep whose leaf uses `uses: ./…`. Because the tool
cannot resolve a ./ inside a remotely fetched composite, the whole chain is
non-onboardable even though every hop before the leaf is a valid $/ ref, and
the block surfaces transitively rather than at the entry action.

- catalog: composite_local_path_blocked_deep (real repo, references the new
  deep-blocked fixture in actions-test-fixtures) asserting the failure names
  the leaf's ./ path.
- unit: TestResolveAllRecursiveCompositeLocalPathErrorDeep exercises the same
  chain offline via seeded tarballs, no token needed.
Adds an end-to-end command test for --migrate-local-actions over a scratch
repo that mixes a local composite ref with a real remote action. It drives
the actual pipeline with a stubbed resolver and asserts both sides of the
mutation in one pass:

- workflow: ./my-action -> $/my-action, while actions/setup-go@v6.0.0 keeps
  its ref (the pin lives in the detached lockfile);
- in-repo composite action.yml files rewritten too, including the nested one
  (./helper -> $/helper, ./nested/deep -> $/nested/deep);
- lockfile: pins actions/setup-go to its resolved commit and records nothing
  for the $/ self refs (they are inherently pinned).

Previous migration coverage only asserted file contents; this closes the gap
by proving the lockfile stays correct across the ./ -> $/ rewrite.
…atalog

The migrate+pin end-to-end behaviour was only asserted at the Go unit level.
This adds an integration-harness scenario that runs the real binary with
--migrate-local-actions over a repo mixing a local composite ref with a
pinnable remote action, and asserts both sides of the mutation:

- the workflow and in-repo composite action.yml files are rewritten from
  ./ to $/ (including a nested composite), and the unresolvable ./nonexistent
  is left alone;
- the lockfile pins the real remote dep (actions/checkout) and records
  nothing for the $/ self refs.

To express this the catalog gains a few capabilities:
- fixtures.files: lay down arbitrary repo-relative files (composite
  action.yml definitions) before the run;
- expect.files_contain / files_exclude: assert substrings in repo files
  after the run;
- expect.lockfile_excludes: assert the lockfile omits a substring (e.g. $/).

Harness and Go catalog schema are extended to match. Existing scenarios are
unaffected.
…wording

Aligns naming with the locked ecosystem decision: internal code identifiers
use the SelfRepository family (dropping the SelfRepo abbreviation), and
user-facing finding-category tokens use the canonical self-reference
phrasing. The frozen contracts (FF literal actions_self_repository, wire
value selfRepository) are not present in this repo and are untouched.

Identifiers renamed (SelfRepo* -> SelfRepository*):
  IsSelfRepoAction, SelfRepoRefHasVersion, SelfRepoRefs, SelfRepoRefErrs,
  SelfRepoAction (category), InvalidSelfRepoRef (category),
  MigrateLocalActionsToSelfRepo, selfRepoPrefix, selfRepoFinding, and their
  tests. selfrepo.go/selfrepo_test.go renamed to selfrepository*.go.

Category tokens (user-facing):
  self-repo-action     -> self-reference-action
  invalid-self-repo-ref -> invalid-self-reference

Integration catalog scenario names self_repo_* renamed to self_reference_*
and the token assertion updated to match. Comments/test descriptions using
"self-repo" reworded to "self-reference".
Adds a "Where does a test belong?" section to AGENTS.md so agents (and
humans) pick the right layer instead of reflexively asserting the same claim
in both Go and the catalog. Captures the two-layer model (hermetic Go vs
real-binary catalog) and the CI pitfalls that have bitten us: needs_token
scenarios only run in the non-blocking live job, only stub-tagged scenarios
run in the blocking stub job, and same-assertion duplication across layers is
cost, not defense-in-depth.
Canonical term is "self repository" ($/ = same repository at running SHA),
not "self reference" — reverting the wording introduced in the prior
follow-up. Go identifiers stay in the SelfRepository family.

Finding-category tokens (user-facing):
  self-reference-action  -> self-repository-action
  invalid-self-reference -> invalid-self-repository-ref

Integration catalog scenarios self_reference_* -> self_repository_* and the
token assertion updated to match. README heading and prose, plus code
comments/test descriptions, reworded from "self-reference"/"self-referencing"
to "self repository" (using "self repository reference" where a noun reads
better). Frozen contracts (actions_self_repository, selfRepository) remain
absent and untouched.
# Conflicts:
#	internal/pipeline/run.go
@nodeselector
nodeselector marked this pull request as ready for review July 24, 2026 03:27
@nodeselector
nodeselector requested a review from a team as a code owner July 24, 2026 03:27
Copilot AI review requested due to automatic review settings July 24, 2026 03:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds $/ self-references to workflow parsing, diagnostics, resolution, migration, and documentation.

Changes:

  • Classifies and validates $/ references.
  • Resolves nested self-references in remote composites.
  • Adds --migrate-local-actions and integration coverage.
Show a summary per file
File Description
README.md Documents self-references and migration.
cmd/gh-actions-lock/format/json.go Filters informational self-reference findings.
cmd/gh-actions-lock/format/terminal.go Renders invalid self-reference errors.
cmd/gh-actions-lock/migrate_test.go Tests migration behavior.
cmd/gh-actions-lock/pin_summary.go Marks invalid references unfixable.
cmd/gh-actions-lock/run.go Adds migration flag and implementation.
cmd/gh-actions-lock/verify.go Supports migration during local verification.
internal/pipeline/checks/category.go Adds self-reference categories.
internal/pipeline/checks/category_test.go Tests category values.
internal/pipeline/checks/finding.go Defines finding validity behavior.
internal/pipeline/checks/parsed.go Stores classified self-references.
internal/pipeline/diagnose.go Reports valid and invalid references.
internal/pipeline/diagnose_test.go Tests diagnostic findings.
internal/pipeline/parse.go Propagates classified references.
internal/pipeline/verify_local.go Validates references offline.
internal/pipeline/verify_local_test.go Tests offline validation.
internal/resolve/discovery.go Traverses same-tarball references.
internal/resolve/resolver_test.go Tests recursive resolution.
internal/workflowfile/rewrite.go Migrates local references.
internal/workflowfile/selfrepo.go Implements syntax detection.
internal/workflowfile/selfrepo_test.go Tests syntax detection.
internal/workflowfile/workflowfile.go Classifies references and discovers actions.
internal/workflowfile/workflowfile_test.go Tests classification and discovery.
test/integration/harness.rb Adds file fixtures and assertions.
test/integration/run.rb Hydrates new fixtures and assertions.
test/scenarios/catalog.go Models new scenario fields.
test/scenarios/catalog.yml Adds end-to-end scenarios.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 27/27 changed files
  • Comments generated: 3
  • Review effort level: Medium

Comment thread internal/workflowfile/workflowfile.go Outdated
Comment thread internal/resolve/discovery.go Outdated
Comment thread internal/workflowfile/rewrite.go
@nodeselector
nodeselector merged commit 2dbe081 into main Jul 24, 2026
11 checks passed
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.

2 participants