Skip to content

Pinning: repair SHA refs from existing lock metadata - #116

Open
nodeselector wants to merge 23 commits into
mainfrom
nodeselector-fix-sha-ref-upgrade
Open

Pinning: repair SHA refs from existing lock metadata#116
nodeselector wants to merge 23 commits into
mainfrom
nodeselector-fix-sha-ref-upgrade

Conversation

@nodeselector

@nodeselector nodeselector commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

What

Repair bare-SHA references in $/ action files when an existing lockfile already records the matching symbolic tag or branch, and preserve existing pins when a workflow hits a terminal structural error.

Fixes #114.
Fixes #121.

Why

Fresh onboarding with v0.1.7-rc.1 rewrites the reported SHA to v3.2.0, but lockfiles generated by v0.1.6 can retain the SHA as the dependency key while storing v3.2.0 in its metadata. The verified-entry path discarded that metadata, so upgrading left the action source at the SHA and regressed the lockfile ref back to it.

Separately, --no-migrate-local-actions returned from diagnosis before carrying existing lock inventory into planning. Fix mode then replaced the affected workflow's pins with an empty list.

How

  • Carry existing tag and branch metadata into verified entries. When a direct verified entry is keyed by a full SHA, reuse that metadata to rewrite the source and rekey the lockfile without another API request.
  • Carry existing direct pins through terminal workflow findings so other workflows can still be fixed without erasing the blocked workflow's lock entry.

Testing

  • Added a command regression covering a nested $/ action and a v0.1.6-shaped lockfile.
  • Added a command regression covering --rescan --no-migrate-local-actions with an existing pinned remote action.
  • go test ./...

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

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

Rekeying a composite dependency can discard its transitive dependency graph.

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 — Rekeying only this entry drops the repaired action's transitive dependency graph. On the fast path,…
What changed in this PR

Repairs SHA-based references using symbolic metadata already stored in the lockfile.

Changes:

  • Preserves tag and branch metadata in verified entries.
  • Rewrites direct SHA references without API requests.
  • Adds an end-to-end regression test.
File Description
internal/​pin/​plan.go Restores symbolic refs from lock metadata.
cmd/​gh-actions-lock/​selfrepository_test.go Tests nested self-repository action repair.

💡 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

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

Partial scans can rewrite a shared local action without updating every affected workflow’s lock entry.

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 internal/​pin/​plan.go — When pinning only a subset of workflows, this rewrite can modify a $/ action file that is also…
Issues resolved since last review (1)
Severity Finding
High severity internal/​pin/​plan.go — Rekeying only this entry drops the repaired action's transitive dependency graph. On the fast path,… 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

Scan-scope detection and dependency-edge preservation can currently reject valid runs or lose transitive lockfile entries.

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

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

New issues introduced by this change (3)
Severity Finding
Medium severity internal/​pin/​plan.go — The partial-scan guard currently rejects the command even when --no-narrow is set (or no tagger…
Medium severity internal/​pin/​plan.go — Inferring scan scope from lockfile keys makes a real full-directory scan look partial whenever the…
High severity internal/​lockfile/​state.go — The SHA-keyed entry is consulted only when the destination symbolic key is absent. If both keys…
Issues resolved since last review (1)
Severity Finding
Medium severity internal/​pin/​plan.go — When pinning only a subset of workflows, this rewrite can modify a $/ action file that is also… View resolved comment

Comment thread internal/pin/plan.go Outdated
Comment thread internal/pin/plan.go Outdated
Comment thread internal/lockfile/state.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

Partial-scan detection can reject valid full scans and unnecessarily fails when narrowing is disabled.

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

Review tier: Balanced
Findings: 1 Medium severity · 2 Low severity

New issues introduced by this change (3)
Severity Finding
Medium severity internal/​pin/​plan.go — This guard also rejects partial scans with --no-narrow, even though narrowVerifiedEntries
Low severity internal/​pipeline/​checks/​parsed.goSelfActionRefs is exported but has no doc comment describing how it differs from Refs and…
Low severity internal/​pipeline/​checks/​finding.goSelfActionRefs is exported but undocumented; its local-action-only scope is important to the…
Issues resolved since last review (3)
Severity Finding
High severity internal/​lockfile/​state.go — The SHA-keyed entry is consulted only when the destination symbolic key is absent. If both keys… View resolved comment
Medium severity internal/​pin/​plan.go — Inferring scan scope from lockfile keys makes a real full-directory scan look partial whenever the… View resolved comment
Medium severity internal/​pin/​plan.go — The partial-scan guard currently rejects the command even when --no-narrow is set (or no tagger… View resolved comment
Suppressed comments (1)

internal/pin/plan.go:91

  • Comparing the report only with current lockfile keys misclassifies a full scan whenever the lockfile contains a deleted workflow. The CLI intentionally discovers those stale keys and prunes them only after Plan, so a normal full run that should prune a deleted workflow will instead hit the partial-scan error when repairing a local action. Pass the invocation's actual full/partial-scan status into planning, or exclude the already-detected stale keys before this check.
	poolErr := pinpool.RunTyped(opts.Pool, ctx, "Planning pins",
		items,
		func(iwr indexedWR) string { return "planning " + iwr.wr.Path },

Comment thread internal/pin/plan.go Outdated
Comment thread internal/pipeline/checks/parsed.go
Comment thread internal/pipeline/checks/finding.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

The global sticky-ref guard can prevent the intended SHA repair, and branch repairs produce inaccurate summary output.

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

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

New issues introduced by this change (2)
Severity Finding
Medium severity internal/​pin/​plan.go — Handle the bare-SHA metadata repair before the sticky imprecise-ref guard. prevImpreciseNWO is…
Low severity internal/​pin/​plan.go — A branch-only metadata repair also sets AutoFixedRef, so Record.Narrowed() passes this entry to…
Issues resolved since last review (3)
Severity Finding
Low severity internal/​pipeline/​checks/​finding.goSelfActionRefs is exported but undocumented; its local-action-only scope is important to the… View resolved comment
Low severity internal/​pipeline/​checks/​parsed.goSelfActionRefs is exported but has no doc comment describing how it differs from Refs and… View resolved comment
Medium severity internal/​pin/​plan.go — This guard also rejects partial scans with --no-narrow, even though narrowVerifiedEntriesView resolved comment

Comment thread internal/pin/plan.go
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

🔵 Needs a closer look

Rekeying can collide with an existing symbolic ref at a different commit and produce order-dependent lock metadata.

Review tier: Balanced
Findings: None

Issues resolved since last review (2)
Severity Finding
Low severity internal/​pin/​plan.go — A branch-only metadata repair also sets AutoFixedRef, so Record.Narrowed() passes this entry to… View resolved comment
Medium severity internal/​pin/​plan.go — Handle the bare-SHA metadata repair before the sticky imprecise-ref guard. prevImpreciseNWO is… View resolved comment
Suppressed comments (1)

internal/pin/plan.go:673

  • Before rekeying to the metadata ref, check whether the lockfile already has that NWO@ref at a different commit. This is especially possible for branch metadata: a SHA entry may record main at an old commit while another workflow records main at its newer commit. Both entries then collapse to the same key in State.Set, which unions incompatible uses graphs and overwrites one commit based on workflow order. Only perform the zero-network repair when the target entry is absent or records the same SHA; otherwise resolve or report the conflict before source files are rewritten.
			e.Ref = newRef
			e.AutoFixedRef = oldRef
			continue
		}
		owner, repo := splitNWO(e.NWO)

@jessehouwing

jessehouwing commented Sep 1, 2026

Copy link
Copy Markdown

I think this is related: #121
Repo is open source, so easy to reproduce: https://github.com/jessehouwing/azdo-marketplace

@nodeselector
nodeselector requested a balanced review from Copilot September 1, 2026 14:59

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

Partial scans can still rewrite unrecorded shared-action refs, and implicit SHA-edge merging can retain stale dependencies.

Review tier: Balanced
Findings: None

Issues resolved since last review (3)
Severity Finding
Medium severity internal/​pin/​plan.go — The lookup is case-insensitive for owner/repository, but the emitted rewrite key uses the… View resolved comment
Medium severity internal/​pin/​plan.go — A lock entry whose metadata ref is itself the bare SHA is loaded with that value in OnBranchView resolved comment
High severity internal/​lockfile/​state.go — Rekeying can collide with an existing symbolic key at a different commit—for example, a SHA entry… View resolved comment
Suppressed comments (2)

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

internal/lockfile/state.go:421

  • Falling back to d.SHA makes this merge run for every symbolic dependency, even when no rekey occurred. If another workflow has an NWO@<sha> entry (for example, for a different sub-action path), its uses edges get copied onto the symbolic entry; after that workflow is pruned, Save still sees those children as reachable and cannot garbage-collect them. Only an explicit OriginalRef proves that this dependency was rekeyed from the SHA entry.
		oldRef := d.OriginalRef
		if oldRef == "" {
			oldRef = d.SHA
		}
		if !isSHARef(d.Ref) && isSHARef(oldRef) {

internal/pin/plan.go:140

  • This guard only examines refs already present in wr.Inventory. A new/unrecorded ref inside a $/ action bypasses it, but the slow path later runs narrowDirectDeps/ReverseLookup and rewriteSelfActionFiles can still mutate that shared action during an explicit-workflow scan. Any unscanned workflow that also uses the action then retains a lock entry for the old ref. Please apply the partial-scan check to planned rewrites targeting SelfActionRefs (including newly resolved refs), not only metadata repairs from inventory.
		for _, inv := range wr.Inventory {
			key := strings.ToLower(inv.Dep.NWO) + "@" + inv.Dep.Ref
			if parserlock.IsFullSha(inv.Dep.Ref) && (inv.Dep.Tag != "" || inv.Dep.Branch != "") && selfActionRefKeys[key] {
				return planResult{}, fmt.Errorf("cannot rewrite %s in a shared local action during a partial workflow scan; scan all workflows", key)

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

Terminal workflows containing only local actions can still have their existing lock entries replaced with an empty list.

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 — This preservation still fails when the blocked workflow contains only ./… actions. ParseAll

Comment thread internal/pipeline/diagnose.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

Conflicting same-run repairs can overwrite pins, and workflow load failures can still erase existing lock entries.

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

Review tier: Balanced
Findings: 3 High severity

New issues introduced by this change (2)
Severity Finding
High severity internal/​pin/​plan.go — This only checks symbolic targets already present in the store. If two SHA-keyed entries in the…
High severity internal/​pipeline/​parse.go — The new terminal-inventory path still misses workflow load failures: the earlier Load error…
Pre-existing issues (1)
Severity Finding
High severity internal/​pipeline/​diagnose.go — This preservation still fails when the blocked workflow contains only ./… actions. ParseAllView comment

Comment thread internal/pin/plan.go
Comment thread internal/pipeline/parse.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

The conflict check can miss a repaired ref colliding with a live symbolic entry at a different SHA.

Review tier: Balanced
Findings: None

Issues resolved since last review (3)
Severity Finding
High severity internal/​pipeline/​parse.go — The new terminal-inventory path still misses workflow load failures: the earlier Load error… View resolved comment
High severity internal/​pin/​plan.go — This only checks symbolic targets already present in the store. If two SHA-keyed entries in the… View resolved comment
High severity internal/​pipeline/​diagnose.go — This preservation still fails when the blocked workflow contains only ./… actions. ParseAllView resolved comment
Suppressed comments (1)

internal/pin/plan.go:112

  • This only compares entries that were themselves auto-fixed. A same-run live entry already using the symbolic ref (for example, action@v3.2.0 resolving to SHA B) has an empty AutoFixedRef, so it is not compared with a metadata repair that maps a SHA key to v3.2.0 at SHA A. Both entries are then committed under the same lockfile key, and whichever workflow is processed last overwrites the shared commit metadata. Include every committed Pinned/Verified entry with a SHA in this consistency check, not only repaired entries.
			if planErr != nil || entry.AutoFixedRef == "" {
				continue

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

Malformed workflows still enter the commit phase and can prevent other workflow fixes while leaving rewritten sources without matching lock updates.

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 — Preserving the inventory here does not keep a malformed workflow out of the write phase. With…
Suppressed comments (1)

internal/pin/plan.go:691

  • The new valid-branch fallback is not exercised: the added branch case only verifies that a SHA-valued branch is rejected, while all successful metadata-repair tests use Tag. Add a table case with Branch: "main" that asserts both the entry rekey and source rewrite, since branch repair is explicitly supported by this path.
			newRef := e.Tag
			if newRef == "" {
				newRef = e.OnBranch
			}

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

🟢 Approval recommended

The repair and preservation paths include appropriate conflict safeguards and focused regression coverage.

Review tier: Balanced
Findings: None

Issues resolved since last review (1)
Severity Finding
High severity internal/​pipeline/​diagnose.go — Preserving the inventory here does not keep a malformed workflow out of the write phase. With… 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.

[0.1.7-rc1] --no-migrate-local-actions removes action references from lock file When locking a repo that contains locally referenced actions...

3 participants