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 keepfix_unreleasedrather 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:
| Level | What triggers it | What it means |
|---|---|---|
| Critical | A 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. |
| High | A 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. |
| Medium | Suspicion 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. |
| Low | None 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_statusbuckets 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/totalcoverage 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 forfix_available, the override forfix_blocked, the advisory's verbatim workaround forfix_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 showspinned → 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, withfix_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). Passupgrade_idsfor 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 formerget_lockfile_upgrade_summary,get_available_upgrades, andget_upgrade_details(themselves the successors to the oldget_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 oldmark_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.