Skip to content

fix(ci): scope package-age gate to npm, add target_url and merge_group - #740

Open
Corina (corinagum) wants to merge 2 commits into
mainfrom
corinagum-upgraded-guacamole
Open

fix(ci): scope package-age gate to npm, add target_url and merge_group#740
Corina (corinagum) wants to merge 2 commits into
mainfrom
corinagum-upgraded-guacamole

Conversation

@corinagum

@corinagum Corina (corinagum) commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Why

package-age/7-day was permanently red on #734 with:

Could not confirm npm age for 4 package(s) (actions/checkout@7.0.1, actions/setup-node@7.0.0, step-security/harden-runner@2.20.1); blocking…

It is a github_actions update, so no package-lock.json changed → the script fell back to parsing the PR body → produced names like actions/checkout@7.0.1 → looked them up on registry.npmjs.org/actions%2Fcheckout404 → tripped the "unverifiable age" fail-safe. A status that could never clear, no matter how many days passed.

CFS quarantine only applies to npm packages resolved through the Azure Artifacts feed. GitHub Actions are fetched from github.com by the runner and are never quarantined, so a fresh action release can't break npm ci.

What changed

Ecosystem scoping. Read the ecosystem from the Dependabot branch slug (dependabot/<slug>/<update>) — chosen over labels because labels are customized in this repo (javascript, not npm_and_yarn) while branch names are not. Note the slug differs from the dependabot.yml key: package-ecosystem: "github-actions" branches as github_actions, and npm as npm_and_yarn.

The skip is an allowlist of ecosystems known not to reach the CFS feed, and the lockfile diff is evaluated before the shortcut. So a changed package-lock.json is always gated regardless of what the branch claims, and an unrecognized slug runs the full check rather than being skipped. This matters because the naive form ("skip anything that is not npm_and_yarn") fails open — see the review thread; caught there and fixed in ba56199.

Status UX. Every status now carries a target_url pointing at the workflow run. That's a real GitHub-hosted page, so it supplies both missing pieces at once: the job log has the untruncated package list (statuses clip at 140 chars), and the run page has a native "Re-run all jobs" button as an on-demand refresh. No page to manufacture, no Check Runs machinery needed.

Freshness. Cron goes from daily to twice daily (17 8,20 * * *), halving how long a cleared package sits yellow.

Merge-queue safety. Added a merge_group: trigger. main has a merge queue, and a required status check must also report on the queue's temporary ref — without this, adding package-age/7-day to the ruleset would stall the queue forever waiting on a status that never arrives. Queued PRs already passed the gate at PR time and packages only get older, so the merge-group path reports success.

Verification

Dry-run harness against live GitHub + npm data, intercepting the status POST:

Case Before After
#734 github_actions failure success — "Ecosystem "github_actions" is not served by the CFS npm feed"
#737 npm_and_yarn, aged deps success via lockfile path
npm PR w/ hono@4.13.3 (1.3d old) pending — "hono@4.13.3 is 1d old (<7d CFS quarantine). Eligible 2026-08-25 UTC."
merge_group event (never reported) success on merge ref
slug npm (unrecognized) + hono@4.13.3 (1.3d) successskipped (fail-open) pending — blocked
unknown slug + young package successskipped pending — blocked

Rows 3-6 are the red-green checks. Row 3 confirms the npm gate still blocks young packages and reports the eligible date. Rows 5-6 are the fail-open regression from review — their Before column is the first commit on this branch (b0a4fc5), run directly against the same inputs to confirm the old logic really did return success. target_url is present on every status.

Workflow YAML re-parsed to confirm merge_group registers as a trigger key (merge_group: null, same shape as the existing workflow_dispatch:).

Follow-up (needs repo admin, not in this PR)

package-age/7-day is not currently merge-blocking — the main ruleset's required_status_checks lists only Build & Test & Lint (24.x). Once this merges, add package-age/7-day to that list to actually block quarantined packages. The merge_group: trigger here is the prerequisite that makes it safe to do.

The gate looked every Dependabot update up on the npm registry. For
`github_actions` PRs no package-lock.json changes, so it fell back to
parsing the PR body, produced names like `actions/checkout@7.0.1`, and
got a 404 from registry.npmjs.org. That tripped the "unverifiable age"
fail-safe and reported a permanent red `package-age/7-day` status that
could never clear (e.g. #734).

Only npm packages resolve through the CFS feed that enforces the
quarantine; actions are fetched from github.com and are never
quarantined. Detect the ecosystem from the Dependabot branch name
(`dependabot/<ecosystem>/<slug>`, which is not customizable unlike
labels) and report a passing "not applicable" status for non-npm
ecosystems. An unrecognized branch shape still runs the full check.

Also improves the status UX and makes the check safe to require:

- Set `target_url` to the workflow run, giving reviewers the untruncated
  package list in the job log plus a native "Re-run all jobs" button to
  refresh the gate on demand.
- Re-check twice daily instead of once, halving how long a cleared
  package stays yellow.
- Add a `merge_group:` trigger so the status also reports on the merge
  queue's temporary ref. Without it, adding `package-age/7-day` to the
  ruleset's required status checks would stall the queue forever.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 19, 2026 19:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the “Dependabot Package Age Gate” workflow so the package-age/7-day commit status is meaningful and actionable across more Dependabot scenarios (especially non-npm ecosystems and merge queue refs), while improving status debuggability.

Changes:

  • Scope the age gate to Dependabot’s npm ecosystem and return a passing “not applicable” status for non-npm ecosystems.
  • Add a merge_group trigger and a merge-queue-safe success path to ensure the required status is reported on merge queue temporary refs.
  • Add target_url on all statuses (linking to the workflow run) and increase scheduled refresh frequency to twice daily.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
.github/workflows/dependabot-package-age-gate.yml Adds merge_group trigger, increases schedule frequency, and clarifies that only npm Dependabot PRs are gated.
.github/scripts/dependabot-pr-age-check.mjs Implements ecosystem scoping, merge-queue status publishing, and target_url support for commit statuses.
Suppressed comments (2)

.github/scripts/dependabot-pr-age-check.mjs:223

  • The inline comment refers to the GitHub Actions ecosystem as github_actions, but Dependabot uses github-actions (and this repo’s dependabot.yml uses that spelling).
  // Only npm packages flow through the CFS feed that enforces the quarantine.
  // `github_actions` (and any other non-npm ecosystem) resolves from github.com,
  // so its versions can never be quarantined -- and looking them up on the npm
  // registry would 404 and wrongly trip the fail-safe.

.github/scripts/dependabot-pr-age-check.mjs:210

  • getDependabotEcosystem currently relies only on the branch name shape. If branch naming is customized or doesn’t match the default pattern, this returns null and the script falls back to parsing the PR body for dependency names—reintroducing the original github-actions 404/fail-safe problem on non-npm PRs. Consider also extracting the ecosystem from the Dependabot PR body (e.g. the package-manager= query param in the compatibility badge) when the branch pattern doesn’t match.
function getDependabotEcosystem(pullRequest) {
  const match = DEPENDABOT_BRANCH_PATTERN.exec(pullRequest.head?.ref ?? '');
  return match?.groups?.ecosystem ?? null;
}

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

Comment thread .github/workflows/dependabot-package-age-gate.yml Outdated
Comment thread .github/scripts/dependabot-pr-age-check.mjs Outdated
Comment thread .github/scripts/dependabot-pr-age-check.mjs Outdated
Review feedback caught a fail-open hole in the ecosystem shortcut. The
skip was expressed as "not in QUARANTINED_ECOSYSTEMS", so *any* slug
other than `npm_and_yarn` reported success -- including an npm PR whose
branch slug ever differs (e.g. `dependabot/npm/...`). A quarantined
package would then sail through with a green check, inverting the
script's fail-safe design.

Two changes:

- Invert the set to NON_NPM_ECOSYSTEMS, an allowlist of ecosystems known
  not to resolve through the CFS feed. Anything unrecognized now runs the
  full check instead of being skipped.
- Evaluate the lockfile diff *before* the ecosystem shortcut. A changed
  package-lock.json is ground truth that npm versions moved, so they are
  gated regardless of what the branch name claims.

Verified against live GitHub/npm data. With slug `dependabot/npm/...` and
a 1-day-old hono@4.13.3, the previous code returned `success` ("not
applicable"); it now returns `pending` with the eligible date. #734
(github_actions) still reports not-applicable and #737 still passes via
the lockfile path.

Also documents that Dependabot's branch slugs differ from the
`package-ecosystem` keys in dependabot.yml (`github-actions` ->
`github_actions`, `npm` -> `npm_and_yarn`), which prompted the review
comments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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