Skip to content

Workflow no longer enforces a cooldown despite its name — @dependabot rebase bypasses Dependabot native cooldown #25

@j7an

Description

@j7an

What

The shared workflow dependency-cooldown.yml is named "Dependency Cool-Down" and consumers reasonably expect it to enforce a minimum release age before a PR can auto-merge. After #20 ("migrate to native Dependabot cooldown"), that enforcement was removed from the workflow and delegated entirely to Dependabot's cooldown.default-days config in consumer repos. The workflow's scan + auto-merge logic has no concept of release age — it only asks GHSA and OSV whether the target version has known advisories.

The consequence: when a Dependabot PR is rebased (either via @dependabot rebase or Dependabot's auto-rebase on conflict), the rebase re-resolves to the latest available upstream versions, which can be inside the cooldown window. The shared workflow then scans, finds no advisories, and enables auto-merge — the cooldown is silently bypassed.

Observed on j7an/nexus-mcp#160 on 2026-04-12:

Action Originally opened with (age at open) Rebased to (age at merge)
step-security/harden-runner v2.16.1 (12 days) ✅ v2.17.0 (3 days) ❌
trufflesecurity/trufflehog v3.94.2 (11 days) ✅ v3.94.3 (4 days) ❌
astral-sh/setup-uv v8.0.0 (14 days) ✅ v8.0.0 (14 days) ✅

cooldown.default-days: 7 is set in nexus-mcp's .github/dependabot.yml, so Dependabot correctly allowed the original PR. But the rebase pulled in 3-day-old and 4-day-old releases that should have been blocked by the 7-day cooldown rule. The shared workflow enabled auto-merge anyway because its advisory scan was clean.

Why

Dependabot's cooldown config gates PR creation, not rebase. @dependabot rebase is documented to re-resolve to the latest available versions without re-running the cooldown check — this is an intended Dependabot behavior, not a GitHub bug. But it means consumer repos that rely solely on Dependabot's native cooldown have a silent bypass path: any rebase (manual or automatic on conflict) can pull in sub-cooldown versions.

Before #20, this workflow (as v1.x) enforced its own cooldown period in the runner, which would have caught this. Post-#20, the workflow's name makes a promise it no longer keeps.

How

Re-introduce release-age enforcement in the shared workflow. The scan step already extracts target versions (added in #23). Extend that code to:

  1. For each extracted (package, target_version) pair, fetch the release publication date via gh api repos/<owner>/<repo>/releases/tags/v<version> (or dereference the tag via git/refs/tags/... + git/tags/... for annotated tags).
  2. Compute age_days = now - published_at.
  3. Compare age_days against a new cooldown_days workflow input (default: 7, configurable per caller).
  4. If any target version is younger than cooldown_days:
    • Render a "Release age" table in the scan comment listing each package, version, age, and threshold
    • Apply a cooldown-pending label to the PR
    • Set the dependency-cooldown / gate status to pending (or failure — caller-configurable), blocking auto-merge
    • Do NOT call gh pr merge --auto even if the advisory scan is clean
  5. If all target versions are ≥ cooldown_days old, proceed with the existing auto-merge flow.

Proposed new input

cooldown_days:
  type: number
  default: 7
  description: "Minimum release age in days before auto-merge is allowed. Set to 0 to disable release-age checking (current behavior)."

Callers on nexus-mcp's current config get a 7-day enforced cooldown by default. Callers who want the old pass-through behavior can set cooldown_days: 0.

Fallback for unknown release dates

If the release date can't be determined (tag not found, API error, version extraction failed), the workflow should treat age as unknown and fail the gate with an explanatory status description. Better to block than silently bypass — the whole point of this fix is closing the silent-bypass path.

Acceptance Criteria

  • Workflow accepts a cooldown_days input (default 7)
  • For each extracted target version, the scan step fetches its release publication date and computes age_days
  • If any target version is younger than cooldown_days, the dependency-cooldown / gate status is not success and auto-merge is not enabled, regardless of advisory scan result
  • The scan comment includes a "Release age" section showing each package, version, publication date, age in days, and pass/fail vs threshold
  • A cooldown-pending label is applied when the cooldown blocks merge, and removed when a subsequent re-scan passes the threshold (see also related issue on stale-label cleanup)
  • When all target versions are ≥ cooldown_days old, behavior is identical to today (advisory-scan-only gating)
  • The gate re-evaluates on every workflow invocation, not just on PR creation — a rebase that pulls in fresh versions must re-check against the threshold
  • Release-date lookup failures fall through to "age unknown → block with explanatory status description", never to "silently allow"
  • Regression verified against a Dependabot PR (or fixture) targeting a sub-cooldown release to confirm the block triggers

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions