Prune deleted workflows from the lockfile on a full scan by nodeselector · Pull Request #87 · github/gh-actions-lock · GitHub
Skip to content

Prune deleted workflows from the lockfile on a full scan - #87

Merged
nodeselector merged 5 commits into
mainfrom
nodeselector-issue-816-deleted-workflows-not-pruned-from-lockfi-5a68ae
Jul 24, 2026
Merged

nodeselector merged 5 commits into
mainfrom
nodeselector-issue-816-deleted-workflows-not-pruned-from-lockfi-5a68ae

Conversation

@nodeselector

Copy link
Copy Markdown
Collaborator

What

Deleted workflows were never pruned from .github/workflows/actions.lock. Their entries lingered forever because discovery only ever sees on-disk files, so a removed workflow was simply never revisited.

Why

State.Save() garbage-collects orphaned dependency entries but never removes orphaned workflow entries. Once a workflow file is deleted, nothing rediscovers its key, so the stale entry stays in the lockfile indefinitely.

Fix

  • internal/lockfile/state.go: add WorkflowKeys() and PruneWorkflows(keep) — PruneWorkflows drops workflow keys not present on disk and relies on the existing Save() orphan-dep GC to clean up now-unreferenced deps.
  • cmd/gh-actions-lock/run.go: on a full-directory scan only (no explicit path args), compute the set of on-disk workflows and prune the rest before commit. Partial/targeted runs never prune, so out-of-scope workflows can't be dropped.
  • Read-only mode surfaces stale entries as a non-blocking stale-workflow info finding instead of removing them.
  • internal/pipeline/checks/category.go / finding.go: add the stale-workflow category (additive) and map it as non-blocking in IsValid().
  • pin_summary.go: report the pruned count.

The category addition is intentionally additive-only to avoid conflicting with concurrent classification work.

Tests

  • internal/lockfile/prune_test.go: pruning removes stale entries + GCs orphaned deps, keeps shared deps, and no-ops when nothing is stale.
  • cmd/gh-actions-lock/prune_workflow_test.go: full scans prune, partial runs don't, and --no-fix reports the stale entry as non-blocking.

Resolves github/actions-dispatch#816

A workflow deleted from .github/workflows/ left its entry lingering in
actions.lock forever. State.Save() garbage-collects orphan dependency
entries but never orphan workflow entries, and discovery only ever sees
files still on disk, so the stale key (and the pins it kept alive) was
never removed. The only workaround was a full from-scratch regenerate,
which also re-pins branch refs as a side effect.

Reconcile the recorded workflow set against disk on a full-directory
scan (no explicit path args) — the only invocation with the authority to
declare a workflow deleted. Fix runs prune the stale keys before the
lockfile is saved, letting the existing orphan-dependency GC drop the
freed pins, and report the count in the summary. Read-only runs
(--no-fix / --verify) surface each stale entry as a non-blocking
stale-workflow info finding instead of modifying anything. Partial
invocations that name specific workflows never prune out-of-scope
entries.

Resolves github/actions-dispatch#816
Copilot AI review requested due to automatic review settings July 13, 2026 22:03
@nodeselector
nodeselector requested a review from a team as a code owner July 13, 2026 22: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.

⚠️ Not ready to approve

Empty full scans still fail before pruning the final deleted workflow.

Pull request overview

Adds full-scan cleanup of deleted workflow entries and orphaned dependencies.

Changes:

  • Adds lockfile workflow enumeration and pruning.
  • Reports stale workflows in read-only and JSON output.
  • Adds documentation and regression tests.
File summaries
File Description
README.md Documents pruning behavior.
internal/pipeline/checks/finding.go Makes stale-workflow findings non-blocking.
internal/pipeline/checks/category.go Adds the stale-workflow category.
internal/pipeline/checks/category_test.go Tests category classification.
internal/lockfile/state.go Adds workflow enumeration and pruning APIs.
internal/lockfile/prune_test.go Tests pruning and dependency GC.
cmd/gh-actions-lock/run.go Integrates full-scan pruning and reporting.
cmd/gh-actions-lock/prune_workflow_test.go Tests full, partial, and read-only scans.
cmd/gh-actions-lock/pin_summary.go Reports the number of pruned workflows.

Review details

  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Medium

Note

Your feedback helps us improve the quality of this feature.
Please use 👍 or 👎 to tell us whether this assessment is correct.


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

Comment thread cmd/gh-actions-lock/run.go
A full scan that discovers zero workflow files aborted in
discoverWorkflowPaths with "no workflow files found" before the lockfile
was ever loaded, so deleting the final workflow stranded its lockfile
entry and orphaned deps indefinitely.

Let a full scan (no explicit paths) proceed with an empty discovery so
the lockfile still loads and the stale entry is pruned. When there's
nothing on disk and nothing recorded, keep the original error. Explicit
path runs are unchanged. Add integration coverage for deleting the last
workflow (fix and --no-fix) and the empty-repo error, plus a shared
catalog scenario for full-scan pruning.
The empty-scan guard ran after the resolver was built, so an empty repo
with no token failed with "authentication token not found" instead of
"no workflow files found" — breaking the no_workflows stub scenario in
CI (which runs without auth). WorkflowKeys() needs no metadata resolver,
so move the guard ahead of resolver construction. The last-deleted-
workflow prune path still proceeds to the resolver as before.
The two "no workflow files found in …" strings were inlined again after
the helper was extracted, so the wording could drift. Call the helper.
…6-deleted-workflows-not-pruned-from-lockfi-5a68ae

# Conflicts:
#	README.md
#	cmd/gh-actions-lock/run.go
#	internal/pipeline/checks/category.go
#	internal/pipeline/checks/category_test.go
#	internal/pipeline/checks/finding.go
@nodeselector
nodeselector merged commit a8682ff 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