What happened
Running validate-action-pins on knight-owl-dev/homebrew-tap surfaces two related issues.
1. Sub-path action refs fail to classify. Actions like Homebrew/actions/setup-homebrew@<sha> are path references — the real repo is Homebrew/actions and setup-homebrew is a directory inside it. The script passes the full Homebrew/actions/setup-homebrew string to the GitHub API as /repos/<owner>/<repo>/..., which 404s. Downstream:
check emits WARN "could not resolve ref " instead of classifying as branch/tag.
list --only=tag / list --only=branch drop the pin entirely because resolve_ref fails and the pin is labeled unknown.
updates still emits the pin (classification is a heuristic via _SEMVER_RE, no API needed), but head_sha silently fails, so branch-pinned sub-path actions never get drift reporting.
2. updates dedups across files. cmd_updates uses _once_per_run to suppress duplicate (action, ref) pairs across every file in the sweep. The docstring documents this, but it's inconsistent with check (per-file) and list (no dedup), and hides that a given pin appears in multiple workflows — information an operator usually wants.
What was expected
- Sub-path actions (
owner/repo/path@ref) should resolve to the containing owner/repo for every API call (resolve_ref, head_sha, list_newer_tags, compare_behind). Classification and drift detection should work identically for them.
updates should report each pin once per file (aligning with check), not once per run.
Steps to reproduce
From knight-owl-dev/homebrew-tap:
validate-action-pins list --only branch .github/workflows/*.yml
# prints no branch pins, though both ci.yml and update-formula.yml reference
# Homebrew/actions/setup-homebrew@<sha> # main
validate-action-pins updates --only branch .github/workflows/*.yml
# prints only the ci.yml line; the identical update-formula.yml pin is silently skipped
Environment
validate-action-pins from the ci-tools image (latest)
- Target repo: knight-owl-dev/homebrew-tap
Notes
Fix sketch:
- Add
_action_repo() helper returning the owner/repo prefix (first two path segments).
- Use it at every API call site:
cmd_check (resolve_ref, compare_behind), cmd_updates (list_newer_tags, head_sha), cmd_list api_mode (resolve_ref).
- Leave
parse_uses_line alone so TSV output keeps the full action reference.
- Change
cmd_updates from _once_per_run to a per-file dedup mirroring cmd_check.
- Add bats coverage: sub-path action classification + multi-file
updates reporting.
What happened
Running
validate-action-pinson knight-owl-dev/homebrew-tap surfaces two related issues.1. Sub-path action refs fail to classify. Actions like
Homebrew/actions/setup-homebrew@<sha>are path references — the real repo isHomebrew/actionsandsetup-homebrewis a directory inside it. The script passes the fullHomebrew/actions/setup-homebrewstring to the GitHub API as/repos/<owner>/<repo>/..., which 404s. Downstream:checkemits WARN "could not resolve ref " instead of classifying as branch/tag.list --only=tag/list --only=branchdrop the pin entirely becauseresolve_reffails and the pin is labeledunknown.updatesstill emits the pin (classification is a heuristic via_SEMVER_RE, no API needed), buthead_shasilently fails, so branch-pinned sub-path actions never get drift reporting.2.
updatesdedups across files.cmd_updatesuses_once_per_runto suppress duplicate(action, ref)pairs across every file in the sweep. The docstring documents this, but it's inconsistent withcheck(per-file) andlist(no dedup), and hides that a given pin appears in multiple workflows — information an operator usually wants.What was expected
owner/repo/path@ref) should resolve to the containingowner/repofor every API call (resolve_ref,head_sha,list_newer_tags,compare_behind). Classification and drift detection should work identically for them.updatesshould report each pin once per file (aligning withcheck), not once per run.Steps to reproduce
From knight-owl-dev/homebrew-tap:
Environment
validate-action-pinsfrom theci-toolsimage (latest)Notes
Fix sketch:
_action_repo()helper returning theowner/repoprefix (first two path segments).cmd_check(resolve_ref,compare_behind),cmd_updates(list_newer_tags,head_sha),cmd_listapi_mode (resolve_ref).parse_uses_linealone so TSV output keeps the full action reference.cmd_updatesfrom_once_per_runto a per-file dedup mirroringcmd_check.updatesreporting.