Safeguard
Open Source Security

How Snyk's CLI test command differs technically from the ...

A technical breakdown of how Snyk's snyk test and snyk monitor commands differ mechanically — exit codes, dependency snapshots, and continuous vulnerability tracking.

Priya Mehta
DevSecOps Engineer
7 min read

Two commands sit at the center of almost every Snyk open source integration: snyk test and snyk monitor. Both consume the same manifest or lockfile, both resolve a dependency graph, and both compare that graph against Snyk's vulnerability database — which leads a lot of engineers to treat them as interchangeable, or to run one without ever realizing what the other does. They are not interchangeable. snyk test is a synchronous, point-in-time check designed to pass or fail a build. snyk monitor is a snapshot-upload operation designed to register a project for ongoing surveillance after that build ships. The difference isn't cosmetic — it changes exit codes, CI placement, and whether you find out about a newly disclosed CVE in an already-deployed dependency. This post walks through the mechanical differences, using Snyk's own publicly documented CLI behavior, so teams can wire both commands into a pipeline correctly instead of guessing.

What does snyk test actually check, and when does it run?

snyk test performs a single, synchronous scan of whatever is in front of it at the moment you invoke the command — a package.json, a pom.xml, a requirements.txt, a built container image, or a Terraform file, depending on which snyk test variant you call. Under the hood, Snyk's CLI plugins (open source at snyk/cli on GitHub under Apache 2.0) resolve the dependency tree using the same logic the package manager itself would use — for npm that means walking package-lock.json; for Maven it means invoking the Maven dependency plugin — then sends that resolved graph to Snyk's API to be matched against its vulnerability intelligence database. The result is printed to the terminal (or emitted as --json or --sarif) and the command exits. Nothing persists on Snyk's servers as a tracked entity. Run snyk test again five minutes later on the same commit and you get the same answer, because it's testing exactly what's checked out right now, not a stored record.

How does snyk monitor differ in what it uploads and when it runs?

snyk monitor uploads that same dependency graph, but instead of returning a pass/fail verdict it registers the snapshot as a persistent "project" inside the Snyk web UI, visible under app.snyk.io/org/<org>/projects. The CLI's own output reflects this: instead of a vulnerability table, snyk monitor prints a URL pointing to the newly created or updated project record. This is why Snyk's documentation consistently recommends running monitor after a merge to a tracked branch (main, release branches) or on a schedule, rather than on every pull request — you're not gating anything, you're checkpointing what's currently deployed or about to be deployed so Snyk has an authoritative, dated record of your dependency tree to compare against later.

Why does snyk test return exit codes but snyk monitor doesn't fail builds?

snyk test is built to be a CI gate, so it follows the same success/failure exit-code convention as a linter or test runner: 0 when no vulnerabilities matching your policy are found, 1 when vulnerabilities are found, and 2 when the command itself failed to run (bad manifest, network error, unsupported project). CI systems key off that exit code to block a merge or fail a build step. snyk monitor, by contrast, is not designed to block anything — its job is to succeed at uploading a snapshot, not to render a verdict about that snapshot's safety. That's a deliberate design choice: if monitor failed the pipeline every time a vulnerability existed, you couldn't use it for its actual purpose, which is tracking dependencies you've already accepted into production and want to be alerted about later, not dependencies you're trying to keep out right now.

How do the two commands handle a vulnerability disclosed after the scan ran?

This is the sharpest technical distinction, and it's easiest to see with a concrete case: CVE-2021-44228 (Log4Shell), publicly disclosed December 9, 2021. Imagine a Java service last scanned with snyk test in November 2021, before the flaw was known — that scan returned a clean exit code 0, and that result is now historical trivia; nobody reruns snyk test against code that already shipped unless a new commit triggers it. Now imagine that same service had also been registered with snyk monitor before December 9. Because the dependency snapshot lives on Snyk's servers as a tracked project, Snyk's backend continuously matches every monitored project's stored dependency graph against its vulnerability database as that database is updated — so when Log4Shell entered the feed, the already-uploaded snapshot would be flagged automatically, and Snyk's alerting integrations (email, Slack, Jira, webhooks) would surface it without anyone touching the CLI again. snyk test only ever knows what was true at invocation time; snyk monitor is the mechanism that lets a scan performed in November still generate a finding in December.

Which CLI flags reveal the underlying design difference?

The flag sets diverge in ways that mirror the "gate vs. track" split. snyk test exposes --severity-threshold=low|medium|high|critical and --fail-on=all|upgradable|patchable to tune exactly what should cause a non-zero exit, because its whole purpose is deciding pass/fail. snyk monitor instead exposes --project-name, --target-reference (used to tag a snapshot with a branch or tag name so multiple monitored versions of the same repo don't collide), --remote-repo-url, and --tags — flags about identity and organization of a persisted record, not about failure conditions. Both commands share --all-projects, --org, and --file, because both still need to resolve the same dependency graph before doing their respective jobs; the divergence starts exactly at the point where one command renders a verdict and the other commits a record.

When should each command actually run in a pipeline?

snyk test belongs in pull request and pre-merge CI jobs, where a non-zero exit code should block the merge, because it's evaluating code that hasn't been accepted into the tracked branch yet. snyk monitor belongs in post-merge jobs on protected branches, or in a nightly/scheduled job against production branches, because its value only exists once there's a snapshot worth watching over time. Running only snyk test means you catch what's known today but go dark the moment a new CVE lands in a dependency you already shipped. Running only snyk monitor means you get long-term visibility but nothing stops a vulnerable dependency from merging in the first place. Snyk's own documentation frames them as complementary for exactly this reason — one is the gate, the other is the smoke detector.

How Safeguard Helps

Understanding this split matters regardless of which scanners sit in your pipeline, because "gate at merge time" and "watch continuously after merge" are two different security properties, and most supply chain incidents (Log4Shell included) are won or lost in the gap between them. Safeguard is built around treating that gap as a first-class problem rather than a byproduct of tool configuration: it correlates dependency inventories, build provenance, and newly disclosed CVEs across your full SDLC, so a component that passed every pre-merge check but later shows up in an advisory doesn't sit unnoticed until someone happens to rerun a scanner. For teams running Snyk, GitHub Dependabot, OSV-Scanner, or any combination of SCA tooling, Safeguard aggregates the resulting signals into a single provenance-backed view, maps affected components to actual deployed services, and helps enforce SOC 2-aligned policies for how a newly disclosed vulnerability in already-shipped code gets triaged and closed — turning "we were technically monitored" into "we actually caught it in time."

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.