semgrep ci: The Diff-Aware CI Subcommand
semgrep ci auto-detects the CI environment and diff, scans only changed code on pull requests, and uploads results when SEMGREP_APP_TOKEN is set.
Where semgrep scan is general purpose, semgrep ci knows it is in a pipeline. It reads the PR base automatically and is diff-aware out of the box.
What it does
semgrep ci detects the provider (GitHub Actions, GitLab CI, etc.), figures out the base ref, and scans only new findings on a pull/merge request. With SEMGREP_APP_TOKEN it pulls your configured rules and uploads findings to the Semgrep platform; without a token it falls back to a default rule set. It exits 1 on blocking findings.
Common usage
# connected to Semgrep AppSec Platform
SEMGREP_APP_TOKEN=${{ secrets.SEMGREP_APP_TOKEN }} semgrep ci
# standalone, no platform, with explicit rules
semgrep ci --config p/ciOptions
| Flag / env | What it does |
|---|---|
| SEMGREP_APP_TOKEN | Auth to pull rules and upload findings |
| --config <src> | Use explicit rules instead of the platform set |
| --sarif --output <f> | Also write SARIF to a file |
| --baseline-commit <sha> | Compare against this commit for new findings |
| --no-suppress-errors | Fail on scan errors instead of skipping them |
In CI
On pull requests semgrep ci reports only newly introduced findings, which keeps PR noise down; on a push to the default branch it scans the full project. It needs the base branch present, so use fetch-depth: 0. semgrep ci already exits non-zero on blocking findings, so no extra --error flag is needed.
Common errors in CI
"Failed to clone the baseline ref" or "could not find baseline commit" means a shallow checkout; set fetch-depth: 0. "Invalid API token" or "401" means SEMGREP_APP_TOKEN is unset or wrong (a fork PR cannot read the secret). With no token and no --config you still get a default scan, which can surprise teams expecting their own rules.