chore(deps): treat every failed release-list response as fatal

Addresses Copilot review round 4 on #2239. Both findings are regressions
I introduced in round 3 and both are correct.

Round 3 moved the lookup from `releases/latest` to the release LIST, to
rank by version rather than by GitHub's designated latest — but kept the
404 suppression that only made sense for the old endpoint. The list
endpoint answers "this action cuts no releases" with a successful empty
array, so a 404 there does not mean that at all: it means the repository
is missing or inaccessible, i.e. a `uses:` ref the sweep cannot check.
Converting it to null silently dropped a broken or renamed action from
the flow that replaced Dependabot — the same silent-success shape this
PR has been closing everywhere else, reintroduced one layer down.

Every non-zero status is now fatal and `isMissingRelease` is gone
entirely; the benign no-releases case is the successful empty array,
covered by its own test.

`buildClearedBody` also overstated its all-clear. Refs pinned to a commit
SHA or a branch return null from `parseVersionRef` and are never ranked,
so "no workflow `uses:` ref is behind" asserted a check that had not
happened for them. It now says version-pinned refs and names the
exclusion explicitly.

Both guards are mutation-checked: restoring the 404 suppression, and
dropping the SHA/branch qualification, each fail exactly one test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGtzPg3UxMLszysQXXfqax
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-03 23:19:58 -04:00 • committed by GitHub
commit dde1736702
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -14,7 +14,6 @@ import {
buildIssueBody,
highestVersionTag,
isActionStale,
isMissingRelease,
main,
parseActionRefs,
parseOutdated,
@ -220,20 +219,6 @@ test("staleActions dedupes, drops actions with no known release and sorts", () =
);
});
test("isMissingRelease suppresses only a 404, never a real API failure", () => {
// A 404 is the legitimate "this action cuts no GitHub releases" answer.
assert.equal(isMissingRelease("gh: Not Found (HTTP 404)"), true);
// Everything else must fail the sweep rather than read as "not stale" — an
// empty actions section is indistinguishable from a healthy run (Copilot).
assert.equal(
isMissingRelease("gh: API rate limit exceeded (HTTP 403)"),
false,
);
assert.equal(isMissingRelease("gh: Bad credentials (HTTP 401)"), false);
assert.equal(isMissingRelease("gh: Server Error (HTTP 502)"), false);
assert.equal(isMissingRelease(""), false);
});
test("buildIssueBody returns null when every install is up to date", () => {
assert.equal(
buildIssueBody([
@ -423,6 +408,9 @@ test("buildClearedBody speaks for both halves of the sweep", () => {
// npm-only wording here would assert an all-clear the sweep never checked.
assert.match(body, /npm package/);
assert.match(body, /uses:/);
// Must not claim every ref was verified: SHA- and branch-pinned refs are
// never ranked against a release (Copilot).
assert.match(body, /SHA or a branch/);
});
test("main fails the sweep when a release lookup errors, rather than reporting no stale actions", () => {
@ -439,11 +427,24 @@ test("main fails the sweep when a release lookup errors, rather than reporting n
);
});
test("main treats a 404 release lookup as 'this action cuts no releases'", () => {
test("main fails on a 404 release lookup — a missing action repo is not 'no releases'", () => {
// The release LIST endpoint answers "no releases" with a successful empty
// array, so a 404 means the repository is missing or inaccessible. Treating
// it as benign would silently drop a broken or renamed action from the sweep
// that replaced Dependabot (Copilot).
const spawn = fakeSpawn({
releasesStatus: 1,
releasesStderr: "gh: Not Found (HTTP 404)",
});
assert.throws(
() => captureLog(() => main("o/r", spawn)),
/release lookup for .* failed/,
);
});
test("main treats an empty release list as 'this action cuts no releases'", () => {
// The benign case: status 0 with no tags. The action is simply not ranked.
const spawn = fakeSpawn({ releaseTags: [] });
const log = captureLog(() => main("o/r", spawn));
assert.match(log.join("\n"), /no-op/);
});