You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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:
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).
Compute age_days = now - published_at.
Compare age_days against a new cooldown_days workflow input (default: 7, configurable per caller).
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
If all target versions are ≥ cooldown_days old, proceed with the existing auto-merge flow.
Proposed new input
cooldown_days:
type: numberdefault: 7description: "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
What
The shared workflow
dependency-cooldown.ymlis 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'scooldown.default-daysconfig 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 rebaseor 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:
step-security/harden-runnertrufflesecurity/trufflehogastral-sh/setup-uvcooldown.default-days: 7is 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
cooldownconfig gates PR creation, not rebase.@dependabot rebaseis 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:
(package, target_version)pair, fetch the release publication date viagh api repos/<owner>/<repo>/releases/tags/v<version>(or dereference the tag viagit/refs/tags/...+git/tags/...for annotated tags).age_days = now - published_at.age_daysagainst a newcooldown_daysworkflow input (default: 7, configurable per caller).cooldown_days:cooldown-pendinglabel to the PRdependency-cooldown / gatestatus topending(orfailure— caller-configurable), blocking auto-mergegh pr merge --autoeven if the advisory scan is cleancooldown_daysold, proceed with the existing auto-merge flow.Proposed new input
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
cooldown_daysinput (default7)age_dayscooldown_days, thedependency-cooldown / gatestatus is notsuccessand auto-merge is not enabled, regardless of advisory scan resultcooldown-pendinglabel 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)cooldown_daysold, behavior is identical to today (advisory-scan-only gating)Related
security-review-neededlabel not cleaned on re-scan (separate issue)astral-sh/setup-uvsilently dropped from scan (separate issue)