Safeguard
Developer Tools•

Why does the security scan pass locally but fail in CI?

A scan can pass locally and fail in CI because of different tool versions, data freshness, lockfiles, scope or config. A checklist to find the difference.

Safeguard Security Team
4 min read

A scan passes locally and fails in CI because the two runs are not the same scan. The usual differences are tool version, advisory data freshness, which files are included, lockfile handling, and configuration. Compare those five and the mismatch is typically easy to find.

Why it matters

When the editor says fine and the pull request says blocked, developers stop trusting one of them, usually both. Matching results is part of the developer experience of a tool, as discussed in what developer experience in security tooling means.

The five usual causes

1. Tool version. The laptop has an older or newer scanner than the pipeline. Rules and parsers differ between versions.

2. Data freshness. The advisory database cached on a laptop may be hours or days old. CI often pulls the latest. A new advisory shows up only in CI.

3. Scope. CI scans the whole repository. The local run covers one folder, or ignores files listed in a local ignore file.

4. Dependencies. Local installs may use a different lockfile state, a different package manager version, or optional packages not present in CI. A floating version range resolves differently on different days.

5. Configuration. A config file in the repository, an environment variable, or a policy set at the organization level may apply only in CI.

A comparison checklist

Print the following from both environments and compare line by line.

ItemHow to check
Tool versionThe tool's version command
Data timestampThe tool's database or advisory update info
Working directory and pathsLog the scan target
Lockfile hashHash the lockfile in both runs
Package manager versionIts version command
Config in effectPrint effective settings if supported
Environment variablesList those the tool reads

Most mismatches appear in the first three rows.

Fixes that last

  1. Pin the tool version in a file in the repository, and use the same in the editor and CI where you can.
  2. Update data on a schedule locally, or run the scan with an update step first.
  3. Commit the lockfile and install from it with the strict install command your package manager provides. See npm ci fails when the lockfile drifts.
  4. Keep config in the repository, not in personal settings.
  5. Print versions and data timestamps in the CI log header, so the next investigation takes one minute.

When the difference is by design

Some gaps are intended. A local run may be faster and shallower for speed, while CI runs the deep scan. That is fine if the tool says so. The message should read "quick check, full scan runs in the pull request," not "passed." Clear labels prevent the false comfort of a quick local pass.

If results still differ

Run the CI container locally with the same image, mount your working tree, and scan. If it reproduces, the cause is environment. If not, the cause is data or config on the CI side. Document the finding in the team docs.

A related effect is that results can change with no code change at all, covered in scan results changed overnight.

Safeguard offers a CLI and IDE extensions that can use the same policies as pull request gates. See the free scan at gold.safeguard.sh/scan.

Quick check

Is the CI result always the right one?

It is usually the authoritative one because it is the gate, but check data freshness and config first. A stale or misconfigured CI run can be wrong too.

Should the local scan block commits?

Generally no. Use it for fast feedback and let the pull request check enforce policy.

Last reviewed: 19 October 2026.

Never miss an update

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

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.