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.
| Item | How to check |
|---|---|
| Tool version | The tool's version command |
| Data timestamp | The tool's database or advisory update info |
| Working directory and paths | Log the scan target |
| Lockfile hash | Hash the lockfile in both runs |
| Package manager version | Its version command |
| Config in effect | Print effective settings if supported |
| Environment variables | List those the tool reads |
Most mismatches appear in the first three rows.
Fixes that last
- Pin the tool version in a file in the repository, and use the same in the editor and CI where you can.
- Update data on a schedule locally, or run the scan with an update step first.
- Commit the lockfile and install from it with the strict install command your package manager provides. See npm ci fails when the lockfile drifts.
- Keep config in the repository, not in personal settings.
- 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.