Skip to content

Rewrite transferred action references to canonical repositories - #118

Open
nodeselector wants to merge 19 commits into
mainfrom
nodeselector-reject-transferred-actions
Open

Rewrite transferred action references to canonical repositories#118
nodeselector wants to merge 19 commits into
mainfrom
nodeselector-reject-transferred-actions

Conversation

@nodeselector

Copy link
Copy Markdown
Collaborator

What changed

  • Preserve GraphQL's canonical nameWithOwner when an action repository was renamed or transferred.
  • Rewrite writable workflow and local $/ action references from the old NWO to the canonical NWO, preserving subpaths and refs.
  • Emit canonical lockfile keys and repository metadata, including during --rescan.
  • Reject redirects found inside remote composites with a typed error naming the old NWO, canonical NWO, and upstream parent.

Why

The CLI previously discarded nameWithOwner, reported a successful lockfile, and left the runner unable to resolve the stale repository identity.

Testing

  • go test ./...
  • ruby test/integration/run.rb transferred_repository_rewritten
  • Fresh onboarding, --rescan, immediate --verify-local, local $/ rewrite planning, remote-composite rejection, and canonical dedup coverage

Fixes #110

Copilot AI balanced review requested due to automatic review settings August 31, 2026 19:19
@nodeselector
nodeselector requested a review from a team as a code owner August 31, 2026 19:19

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.

Copilot review overview

🟡 Changes recommended

Mandatory rewrites can be silently skipped, and dependency filtering can invalidate transfer directness tracking.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 2 Medium severity

New issues introduced by this change (2)
Severity Finding
Medium severity internal/​pin/​plan.go — A transfer rewrite must not be best-effort. RewriteActionRefs skips anchored uses: scalars, and…
Medium severity internal/​pin/​plan.go — Rebuild the rewrite tracker after lookupIssues filter deps: filtering can shift indices, while…
What changed in this PR

Canonicalizes renamed or transferred action repositories across resolution, source rewriting, and lockfile generation.

Changes:

  • Preserves original and canonical repository identities during resolution.
  • Rewrites direct workflow and local-action references while rejecting remote-composite transfers.
  • Adds canonicalization and integration coverage.
File Description
test/​scenarios/​catalog.yml Adds a transfer rewrite scenario.
internal/​resolve/​discovery.go Tracks original refs and transfer errors.
internal/​pipeline/​diagnose.go Exposes live resolution details.
internal/​pipeline/​checks/​resolver.go Resolves original aliases from prewarmed data.
internal/​pipeline/​checks/​finding.go Extends workflow reports.
internal/​pin/​plan.go Plans canonical rewrites and transfer validation.
internal/​pin/​plan_test.go Tests transfer planning.
internal/​lockfile/​direct_tracker.go Recognizes transferred direct refs.
internal/​ghapi/​graphql_action_files.go Reads canonical GraphQL repository names.
internal/​ghapi/​graphql_action_files_test.go Tests GraphQL canonicalization.
internal/​dep/​dependency.go Stores and merges original refs.
internal/​dep/​dependency_test.go Tests transferred-source deduplication.
cmd/​gh-actions-lock/​command_test.go Tests CLI rewriting and rejection flows.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/pin/plan.go Outdated
Comment thread internal/pin/plan.go Outdated

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.

Copilot review overview

🔵 Needs a closer look

REST-based resolution paths still lose canonical repository identity and can retain stale transferred references.

Review tier: Balanced
Findings: None

Issues resolved since last review (2)
Severity Finding
Medium severity internal/​pin/​plan.go — Rebuild the rewrite tracker after lookupIssues filter deps: filtering can shift indices, while… View resolved comment
Medium severity internal/​pin/​plan.go — A transfer rewrite must not be best-effort. RewriteActionRefs skips anchored uses: scalars, and… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

internal/ghapi/graphql_action_files.go:32

  • Transferred repositories are still missed whenever ResolveActionFiles uses its REST-only or anonymous fallback path. resolveAnonymous returns the requested Owner/Repo and never sets OriginalNWO, even though its HTTP client follows repository redirects, so the resolver creates no OriginalRefs; the workflow and lockfile therefore retain the stale NWO (and remote-composite redirects are not rejected). Capture the canonical repository from the REST response/final URL and populate these identity fields consistently for both resolution paths.
	Owner       string
	Repo        string
	OriginalNWO string

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.

Copilot review overview

🟡 Changes recommended

Read-only verification modes do not surface transferred repositories because transfer validation occurs only during planning.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity

New issues introduced by this change (1)
Severity Finding
High severity internal/​pipeline/​diagnose.go — Transfer identity is only retained for pin.Plan, but read-only commands return immediately after…

Comment thread internal/pipeline/diagnose.go

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.

Copilot review overview

🟡 Changes recommended

Read-only verification misses transfers, while partial failures can misclassify successful redirects and retain stale lock entries.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 2 Medium severity

New issues introduced by this change (2)
Severity Finding
Medium severity internal/​pin/​plan.go — A successful redirect is represented only by its canonical key, but unresolvedEntries below marks…
Medium severity internal/​pipeline/​checks/​resolver.go--no-fix/--verify returns before the planner runs (cmd/gh-actions-lock/run.go:331-357), and…
Issues resolved since last review (1)
Severity Finding
High severity internal/​pipeline/​diagnose.go — Transfer identity is only retained for pin.Plan, but read-only commands return immediately after… View resolved comment

Comment thread internal/pin/plan.go
Comment thread internal/pipeline/checks/resolver.go

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.

Copilot review overview

🟡 Changes recommended

Existing stale lockfiles still hide transfers during normal runs because resolver cache prewarming bypasses canonical identity discovery.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity

New issues introduced by this change (1)
Severity Finding
High severity internal/​pin/​plan.go — Normal runs with an existing lockfile still miss this transfer.…
Issues resolved since last review (2)
Severity Finding
Medium severity internal/​pipeline/​checks/​resolver.go--no-fix/--verify returns before the planner runs (cmd/gh-actions-lock/run.go:331-357), and… View resolved comment
Medium severity internal/​pin/​plan.go — A successful redirect is represented only by its canonical key, but unresolvedEntries below marks… View resolved comment

Comment thread internal/pin/plan.go

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.

Copilot review overview

🟡 Changes recommended

REST resolution and default mutable-ref fast paths can still preserve stale transferred repository identities.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity cmd/​gh-actions-lock/​run.go — A normal run still skips live resolution when an existing lockfile contains only mutable refs such…
Issues resolved since last review (1)
Severity Finding
High severity internal/​pin/​plan.go — Normal runs with an existing lockfile still miss this transfer.… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

internal/ghapi/graphql_action_files.go:33

  • Repository transfers are still invisible in the supported REST-only/anonymous fallback path. ResolveActionFiles calls resolveAnonymous when GH_ACTIONS_LOCK_DEPENDABOT_PROXY=1 (and for eligible GraphQL failures), but that function always returns the requested owner/repo and never sets OriginalNWO; the HTTP client follows GitHub's redirect, so resolution succeeds under the stale identity and no rewrite is planned. Capture the final redirected NWO (or fetch canonical repository metadata) in the REST path and populate these fields consistently.
	Owner       string
	Repo        string
	OriginalNWO string
	Path        string

Comment thread cmd/gh-actions-lock/run.go

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.

Copilot review overview

🟡 Changes recommended

Deduplication can retain stale dependency data, and rewrite validation can modify or reject non-action uses fields.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity

New issues introduced by this change (1)
Severity Finding
High severity internal/​dep/​dependency.go — This keeps every field from the first dependency and only merges provenance. In a partially…
Issues resolved since last review (1)
Severity Finding
Medium severity cmd/​gh-actions-lock/​run.go — A normal run still skips live resolution when an existing lockfile contains only mutable refs such… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

internal/workflowfile/rewrite.go:141

  • This traversal treats every mapping key named uses as an action reference, whereas extraction intentionally ignores locations such as env.uses and with.uses (workflowfile.go:422-426, workflowfile_test.go:434-449). If one of those data fields contains the same old reference, an anchor there can incorrectly block an otherwise writable transfer; without an anchor it is counted and rewritten as ordinary data. Restrict required matching and rewriting to the GitHub Actions-supported uses locations used by walkUses.
				if keyNode.Value == "uses" {
					target := valueNode
					if valueNode.Kind == yaml.AliasNode && valueNode.Alias != nil {
						target = valueNode.Alias
					}
					if target.Kind == yaml.ScalarNode {
						oldValue := strings.TrimSpace(target.Value)
						if _, ok := replacements[oldValue]; ok {
							matches[oldValue]++
							if anchored || valueNode.Kind == yaml.AliasNode || target.Anchor != "" {
								blocked = oldValue
							}
						}
					}
				}
				walk(valueNode, anchored, depth+1)

Comment thread internal/dep/dependency.go

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.

Copilot review overview

🟡 Changes recommended

A known REST-detected redirect can still pass verification when subsequent GraphQL resolution fails.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity

New issues introduced by this change (1)
Severity Finding
High severity internal/​pipeline/​run.go — A successful identity lookup that returns a different canonical NWO is reduced to false, so the…
Issues resolved since last review (1)
Severity Finding
High severity internal/​dep/​dependency.go — This keeps every field from the first dependency and only merges provenance. In a partially… View resolved comment

Comment thread internal/pipeline/run.go

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.

Copilot review overview

🟡 Changes recommended

Identity lookup and resolution failures can still allow stale transferred references to pass or finish successfully without rewriting.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity · 1 Medium severity

New issues introduced by this change (2)
Severity Finding
High severity internal/​pipeline/​run.go — When REST identifies a transfer but GraphQL resolution fails, this adds only a RefChanged finding…
Medium severity internal/​pipeline/​run.go — A failed repository-identity request is converted to canonical == "", and this condition then…
Issues resolved since last review (1)
Severity Finding
High severity internal/​pipeline/​run.go — A successful identity lookup that returns a different canonical NWO is reduced to false, so the… View resolved comment

Comment thread internal/pipeline/run.go Outdated
Comment thread internal/pipeline/run.go Outdated

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.

Copilot review overview

🟡 Changes recommended

REST fallback and mutable-lockfile transitive paths can still preserve stale repository identities.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 High severity · 1 Medium severity

New issues introduced by this change (2)
Severity Finding
High severity internal/​pipeline/​run.go — This only validates repositories named by the workflow's direct mutable refs. For a fully recorded…
Medium severity internal/​ghapi/​graphql_action_files.go — Canonicalization only occurs for GraphQL results. ResolveActionFiles also returns…
Issues resolved since last review (2)
Severity Finding
Medium severity internal/​pipeline/​run.go — A failed repository-identity request is converted to canonical == "", and this condition then… View resolved comment
High severity internal/​pipeline/​run.go — When REST identifies a transfer but GraphQL resolution fails, this adds only a RefChanged finding… View resolved comment

Comment thread internal/pipeline/run.go Outdated
Comment thread internal/ghapi/graphql_action_files.go Outdated

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.

Copilot review overview

🟡 Changes recommended

Recorded stale transitive edges can incorrectly produce blocking transfer findings after live resolution proves they were removed.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 2 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity internal/​pipeline/​run.go — This reintroduces transfers found only in the recorded lockfile closure even after live resolution…
Pre-existing issues (1)
Severity Finding
Medium severity internal/​ghapi/​graphql_action_files.go — Canonicalization only occurs for GraphQL results. ResolveActionFiles also returns… View comment
Issues resolved since last review (1)
Severity Finding
High severity internal/​pipeline/​run.go — This only validates repositories named by the workflow's direct mutable refs. For a fully recorded… View resolved comment

Comment thread internal/pipeline/run.go

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.

Copilot review overview

🔵 Needs a closer look

The security-sensitive resolver, rewrite, and lockfile changes span multiple interacting execution paths and warrant final human validation.

Review tier: Balanced
Findings: None

Issues resolved since last review (2)
Severity Finding
Medium severity internal/​pipeline/​run.go — This reintroduces transfers found only in the recorded lockfile closure even after live resolution… View resolved comment
Medium severity internal/​ghapi/​graphql_action_files.go — Canonicalization only occurs for GraphQL results. ResolveActionFiles also returns… View resolved comment

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.

gh actions-lock CLI and GitHub Actions Runner Disagree on Moved Repo Resolution

2 participants