Available upgrades

How DevUpdate.io surfaces available dependency upgrades, how risk is aggregated across the upgrade range, and how the auto-resolve / dismiss lifecycle works.

An available upgrade is a proactive heads-up that one of your monitored dependencies is trailing the latest known release of its tracked source. For every dependency whose pinned version sits behind the newest release we know about, DevUpdate.io surfaces the upgrade as pinned → latest, tied back to the version you're actually on. The heads-up is "your react@18.2.0 can move to 19.1.0," not just "react updated."

This replaces the old reactive "lockfile alert" model. Instead of waiting for a version to change between syncs and firing a one-off alert per change, we tell you up front what you could upgrade to, and we keep that picture current as releases land.

How an upgrade is computed #

For each monitored dependency we compare the version you pin against the latest release of its tracked source. When the latest release is newer, an available upgrade is offered.

Versions are compared under the ecosystem's own convention (semver for npm/Cargo/Go/Composer lockfiles, PEP 440 for Python), and the offered version always keeps the release tag's own spelling. (Treating npm tags as Python versions used to misread prerelease tags like 15.0.0-0 as post-releases; that's gone.)

Offered versions are also validated against the package's own registry: a release tag that names a version the package never published on npm/PyPI cannot become an upgrade target. This is what stops monorepo sources from leaking another package's versions into your list: client-only resolves to the React monorepo, whose v19.x tags are React's versions, not client-only's. When none of a source's tags match the registry, the package produces no upgrade at all rather than a fictional one.

Risk is aggregated over every release in the (pinned, latest] range, not just the newest one. If a security fix shipped three versions back and a breaking change two versions back, both still count toward the upgrade's risk even when the newest release itself is a benign patch. The upgrade carries flags for whether the range includes a major version jump, a verified security fix, or a breaking change, plus an aggregated risk score and a link to the source's full release history so you can see exactly what's in the jump.

Security flags are advisory-verified #

The security flag is grounded in registry advisories, not release-note prose: it means an OSV/GHSA advisory, the same data npm audit and pip-audit read, affects the version you pin, and its fix ships inside the upgrade range. Each flagged upgrade carries the advisory ids, severity, and the fixing version (linked from the upgrade card), so you or your agent can verify the claim instead of taking our word for it. Release notes that merely mention security work (upstream CI hardening reads as "security" to a summarizer) no longer drive the flag; they surface in the upgrade's reason as an unverified signal instead.

Security issues: the cases no upgrade can carry #

An upgrade row can only exist when your pin trails latest, which means two real vulnerability situations have nowhere to live on an upgrade list:

  • Vulnerable at latest. Your pin is the newest release, and an advisory affects it. Nothing to upgrade to; still exposed.
  • Fix unreleased. An advisory affects your pin, and no fixed version is released and reachable above it: the maintainer hasn't shipped a fix, or the version the advisory names isn't published on the registry yet. (A fix the registry has published above your pin reports as fix_available, even when our last release sync hadn't caught it yet. We check the registry's published set directly, independently of how far our release ingestion has got, so a fix that just shipped never masquerades as "no fix". That set is cached briefly (up to an hour) to keep a recompute from re-downloading it per package, which bounds the check's lag to that window rather than to a release-sync cycle. When that registry check is unavailable we stay conservative and keep fix_unreleased rather than assume a fix exists, so cross-check the registry before accepting the risk.)

DevUpdate.io tracks these as security issues, separately from upgrades: one issue per (lockfile, package, advisory) affecting a pinned version, refreshed daily against OSV. Each issue carries its advisory ids, severity, references, and a fix_status that tells you what resolving it takes: fix_available (a released upgrade delivers it, linked once we've ingested that release), fix_blocked (every released fix for this advisory is excluded by a parent constraint; the constraint and its override cure are named), or fix_unreleased. When the advisory documents a workaround, we include the maintainer's own text verbatim, never a paraphrase.

Blocked-ness is judged against the advisory's own fix, not against the package's latest version. An advisory is often fixed on several branches at once: picomatch's ReDoS is fixed in 2.3.2, 3.0.2 and 4.0.4, so a parent requiring ^2.3.1 blocks the jump to 4.x while still admitting the fix at 2.3.2. That issue is fix_available at 2.3.2 even though its upgrade row is correctly blocked, and we name the lowest fix you can actually reach rather than the highest one the advisory lists. When every fix really is excluded, the constraint we name is the one blocking the lowest of them: the requirement to override.

A constraint has to clear two tests before we name it as your blocker. It must admit the version you have installed — proof that it governs your resolution — and exclude the fix — proof that it's what stops the move. The first test matters because npm installs one package name at several versions at once (a hoisted copy plus nested conflicting ones, the duplicates we count as shadowed), while the dependency graph records constraints per package name. So a brace-expansion pin of 1.1.12 with its fix in 1.1.16 also picks up minimatch requires ^5.0.2 from the unrelated 5.x copy: that range does exclude 1.1.16, but it excludes 1.1.12 too, which means it never governed your pin and the 5.x branch has its own patched release. Constraints like that are dropped rather than printed. Both tests need a definite answer — a requirement we can't evaluate (a workspace:, file: or git dependency) proves neither, so it never produces a blocked verdict.

Advisory coverage spans every pinned dependency, not just the ones with a monitored source: an OSV match needs only the ecosystem, package name, and version, so a hand-uploaded lockfile is audited on upload even before any source is discovered. (A non-source pin can still resolve to any of the three statuses, including fix_blocked, which needs the parsed dependency graph but not an upgrade row. Only the direct upgrade link requires the upgrade computation, which is source-based.)

When that daily OSV re-check (or a lockfile sync) turns up a newly-disclosed critical or high severity advisory against one of your pins, we email you a heads-up, deep-linking to the lockfile's Security issues section. If a single disclosure lands in several of your lockfiles at once, they're consolidated into one email (each affected lockfile listed with its own link), never one email per lockfile. Only first-seen advisories trigger it: routine re-scans of an issue you've already been told about don't re-email, and the email is skipped entirely if your notification frequency is set to disabled (and stays skipped — turning notifications back on later doesn't deliver a backlog of alerts you were deliberately not sent). It's an immediate alert, not part of the daily digest, so an exposure never waits for your next digest window. Moderate and low severity advisories are tracked in full on the dashboard but don't email — the interrupt is reserved for the bands you'd want to act on the day they're disclosed.

Each issue records whether it has been alerted on, rather than that being decided in the moment we detect it. So if a send fails — our mail provider has a blip, we redeploy mid-sweep — the alert is still outstanding and goes out on the next cycle instead of being lost. Delivery is retried a few times and then stops, so a mailbox that's permanently rejecting our mail doesn't get retried forever.

We bias that machinery toward reaching you: if a send is accepted but we lose track of it before recording the fact (a deploy landing at exactly the wrong moment), you may see the same alert a second time. Given the choice between an occasional duplicate and a security alert that silently never arrives, we'd rather send it twice.

Nothing else emails you about a lockfile. In particular, a newly-available upgrade never sends mail, whatever its risk level. A "high-risk" upgrade means risky to adopt — a major jump or documented breaking changes — which is the opposite of urgent: it's a reason to read the release notes before you move, not a reason to be pulled out of what you're doing. Those live on the lockfile page and in the MCP tools, where you pick them up when you're actually doing dependency work. Keeping the mail channel to advisory-backed exposure is what makes an email from us worth opening: if it arrives, something you pin is vulnerable.

Security issues deliberately have no dismissal: an advisory that still affects your pin keeps listing on every audit until the pin moves out of the vulnerable range or the advisory is withdrawn. Accepted risks belong in your repo's own risk register, not silenced in the data source. You read them on the lockfile detail page (see below), and agents read the same list via the get_security_issues MCP tool. The devupdate-audit skill turns it into a complete audit workflow.

Direct, transitive, and blocked upgrades #

For lockfile formats that carry a dependency graph (package-lock.json v2/v3 and uv.lock today), each upgrade is annotated as direct (the manifest's own dependency, yours to move) or transitive (pinned by the resolver). For transitive upgrades we additionally check every dependent's version constraint against the offered version: when a parent both admits your pinned version and excludes the offered one (aioboto3 exact-pins aiobotocore, next-auth caret-pins uuid), the upgrade is marked blocked by parent, with the constraint shown: it is not independently installable, so the fix is to upgrade the parent or, for security fixes, to add an override/resolution. The same two-test rule as security issues above applies — a range that doesn't cover your pin belongs to another copy of the package in the tree and is never named. The stat strip splits the headline "Upgradeable" count into direct vs. blocked so a large number of parent-gated transitive pins can't masquerade as actionable work.

Upgrades are (re)computed at three moments:

  • when monitoring starts for a dependency (after its source is discovered),
  • on each sync or re-upload of the lockfile, and
  • whenever a new release lands for a monitored source.

"Not monitored" dependencies #

A dependency only produces upgrades once its source has been discovered and is being monitored. Dependencies whose source we haven't resolved (no public GitHub repo found, an unsupported host, or simply not selected for tracking) are counted as not monitored and never generate an upgrade. They show up in the rollups so you can see the gap, and no upgrade row appears for them until a source is attached. See Package matching for how discovery works and why some packages stay unmatched.

This is a release-intelligence gap, not a scanning gap. A dependency with no tracked source gets no release notes, no risk scoring, and no upgrade row, but its pinned version is still matched against OSV/GHSA advisories, because that match needs only the ecosystem, name, and version (see Security issues above). Select two packages out of eight hundred and the security audit still covers all eight hundred. That's why the lockfile's stat strip states Release tracking as a ratio (2/836) with the advisory coverage named underneath it, instead of a bare "Unmonitored: 834" that reads like a hole in the audit.

Monitoring is counted across your whole account, not per lockfile: if a package in a freshly added lockfile is already actively watched through another lockfile (or was added as a source directly), it counts as monitored right away: the "Release tracking" ratio in the stat strip and the per-package "Monitoring" badges always agree.

The lifecycle: auto-resolve and dismiss #

There is no read/unread state and no "Mark all as read" anymore. An available upgrade has a simple lifecycle:

  • Auto-resolve. When a later sync shows the dependency was bumped to (or past) the offered version, the upgrade disappears on its own. Bump the dependency in your project, let the next sync pick it up, and the upgrade clears itself.
  • Dismiss. Not ready to move? Dismiss the upgrade to snooze it. A dismissed upgrade stays hidden until a newer version than the one you dismissed appears, at which point it resurfaces with the new target. You rarely need to dismiss after actually upgrading. The next sync auto-resolves it for you.

Risk levels #

An upgrade's level is a priority, computed from the signals aggregated over the whole (pinned, latest] range. Critical is reserved for urgency (you're behind a security fix), High for confirmed breaking signals; softer suspicion signals cap at Medium so that long upgrade ranges (which almost always contain some noise) don't all read as high-risk. The levels:

LevelWhat triggers itWhat it means
CriticalA registry advisory (OSV/GHSA) affects your pinned version and its fix ships in the range, verifiable by the advisory ids on the upgrade.You're provably on the vulnerable side of a known fix. Take it first; if a parent blocks it, take it through the parent or an override.
HighA confirmed breaking signal anywhere in the range: the upgrade crosses a major version, or a release documents breaking changes.Risky to take: read the releases in the range before upgrading.
MediumSuspicion without confirmation: undocumented changes we detected in a diff, security-flavored release notes with no matching registry advisory, an advisory whose fix is not in this range, or an elevated per-release risk score (40+).Worth a look; usually safe to batch and verify with your test suite.
LowNone of the above: patch/minor releases with quiet diffs.Routine catch-up.

A verified security fix outranks everything else: a major jump that also ships the fix is critical, not merely high: urgency dominates, and the breaking signals still show in the upgrade's flags and reason.

Where upgrades show up #

The UI is aggregates-first: you start from a portfolio-wide rollup, narrow to per-lockfile counts, and drill down to per-dependency detail.

  • Across your lockfiles: the panel at the top of Pulse sums the picture over every lockfile you own, split into the same three fix_status buckets used below (exposed with no fix, fix blocked, fix available) plus high-risk, parent-blocked and total available upgrades. Selecting one lists the lockfiles it comes from, biggest first, each linking straight into the relevant section here. It's the answer to "do I have to open all fifteen lockfiles to know if anything is wrong?".
  • Pulse overview: the Pulse page rolls up each lockfile's picture into per-lockfile counts: how many dependencies are upgradeable, and of those how many involve a major jump, a security upgrade, or are otherwise high-risk, plus how many dependencies are without release tracking (the not-monitored count: no source, so no release notes or upgrades; their advisories are still matched), and, separately, how many open security issues the lockfile has and how many are exposed with no upgrade path (fix_unreleased). A lockfile whose only signal is a security issue with no available upgrade still appears in this rollup. Click through to drill into a lockfile.
  • Lockfile detail: the header shows a stat strip with the same counts (dependencies / upgradeable / major / open issues / security upgrades / high-risk / release tracking) for that one lockfile: open issues is the count of open security issues (the audit), distinct from security upgrades (upgrade rows that carry a security fix), and release tracking is the monitored/total coverage ratio described under "Not monitored" dependencies. Directly below the strip, a Security issues section lists each open issue grouped by fix status, with the "exposed: no upgrade path" (fix_unreleased) group leading. Those groups are collapsible, and each header carries its issue count plus a severity breakdown, so an audit with dozens of advisories reads as a few headline rows instead of a wall of cards you have to scroll past to reach the rest of the page. Short groups (three issues or fewer) start expanded; longer ones open on demand and reveal their cards a page at a time, most severe first. Each card shows its severity, fix_status, osv.dev-linked advisory chips, and the cure (the upgrade for fix_available, the override for fix_blocked, the advisory's verbatim workaround for fix_unreleased). Below that, the upgrades are grouped into collapsible sections by risk level (critical / high / medium / low) so a long list reads as a handful of headline counts first. All of them start collapsed, critical included, so the first thing you see is the shape of the lockfile (how many upgrades at each risk level, and how many of those carry a security fix or a major jump), not a wall of cards; you open the group you want to work on. Each upgrade row shows pinned → latest, its flags, and its aggregated risk. The Resolve via MCP call-to-action sits at the top of the upgrades section, above the groups, since pointing an agent at the list is the fastest way to work through it. Below the upgrades, Package Sources is where you choose what's release-tracked; the dependency and release-tracking stat cards link straight to it.
  • Per-dependency drill-down: every row links to the source's release history at /dashboard/source/[id], where you can read each release in the (pinned, latest] range to understand the full scope of the upgrade before you take it.

Acting on an upgrade #

Open the source's release history from the upgrade row to see every release between your pinned version and the latest, with summaries, diffs, and the signals that drove each score (see Risk scoring). Decide whether to upgrade now, pin around a specific release, or wait for a follow-up.

When you do upgrade and the next sync reflects it, the upgrade auto-resolves. If you want to hold off, dismiss it to snooze until a newer version lands.

From an AI agent (MCP) #

If you've wired up the MCP server, an agent can audit and work your upgrades end to end:

  • get_security_issues(lockfile_id?): every advisory affecting a pinned version, independent of upgrade availability, with fix_status, blocked-by constraints, and verbatim advisory workarounds. The agent-side audit entry point: the same list the lockfile detail page's Security issues section renders, so vulnerable-at-latest and unreleased-fix cases that no upgrade row can carry are visible to both humans and agents.
  • get_upgrades(lockfile_id?, risk_level?, upgrade_ids?, include_dismissed=False): available upgrades in one call. By default a cheap per-lockfile rollup (the same counts shown on Pulse and the detail header, plus the direct / transitive / blocked-by-parent split) leads the per-package list (pinned → latest, major / security / breaking / direct / transitive / blocked flags, OSV/GHSA advisory ids with fix versions, and the parent constraints behind blocked upgrades, each (id: N)-tagged). Pass upgrade_ids for release-by-release detail across each upgrade's (pinned, latest] range: the agent equivalent of opening the release history before a risky upgrade. Folds in the former get_lockfile_upgrade_summary, get_available_upgrades, and get_upgrade_details (themselves the successors to the old get_alerts).
  • dismiss_upgrades(lockfile_id?, upgrade_ids?): snooze upgrades until a newer version appears, scoped to one lockfile or a specific set of upgrade ids. This replaces the old mark_alerts_read.

A lockfile whose Available to AI agents toggle is off (see Live connection) is excluded from all of the above: its issues and upgrades don't appear in get_security_issues / get_upgrades, and it's dropped from the rollup counts, even though it stays fully visible on your dashboard.

A common agent flow starts with get_security_issues (anything affecting a pin gets handled first, by fix_status), then get_upgrades unfiltered (its rollup tells you which lockfiles need attention; pass upgrade_ids before anything major or breaking), and resolves the ones it can. After actually bumping a dependency you usually don't need to call dismiss_upgrades at all. The next sync auto-resolves the upgrade. Use dismiss_upgrades only when you want to snooze an upgrade you've decided to defer.

The whole flow ships as downloadable agent skills (audit, upgrades, and security triage) you can drop into your repository, so any Claude Code session knows the safe order of operations without prompting.

Frequently asked questions #

Is this the same as a CVE scanner or SCA tool? #

We overlap where it matters and go further where they stop. Like an SCA tool, we match OSV/GHSA advisories against the exact versions you pin, with advisory ids attached so you can verify every flag on osv.dev. Unlike a scanner, we tell you what to do next: whether the fix is takeable, blocked behind a parent constraint (and which override cures it), or unreleased (and what the maintainer's documented workaround is), plus the release-by-release intelligence to survive the upgrade and the ecosystem context no scanner carries. And we do it without scanning your code: exposure checks in your codebase are your agent's job, locally, guided by our skills. Scan-and-score tools stay complementary for install-time malware blocking; use us to know what's wrong, what it takes to fix, and whether the fix is safe to merge.