Every advisory banner PullGuard prints, what it means, and what to do about it — plus how to tell a deliberate zero from a broken setup.
pullguard doctor
Most "why didn't PullGuard do X?" questions are answered by the environment, not the code.
doctor checks it in one command — read-only, and your license key is always masked in its output:
pullguard doctor . # human-readable
pullguard doctor . --json # machine-readable
.driftrc.yml was found and whether it parses.It exits non-zero only when a check failed (a scan would run degraded), so you can use it as a CI setup gate.
When a scan can't run at your paid tier it says so at the top of the report and degrades to the free tier for that one scan — your subscription is never affected by a failed check.
The banner names the cause. Network: your runner could not reach
pullguard.dev/api/validate — check the runner's outbound HTTPS allowlist or firewall.
Service error: usually a brief outage on our side; the next scan retries automatically.
Persisting? hello@pullguard.dev.
Scans run at the free tier until renewal. A warning appears in reports for the last 14 days before expiry so this never lands as a surprise mid-sprint.
In GitHub Actions this is automatic. In other CI or local runs, set
PULLGUARD_REPO=<org>/<repo> (or scan inside a git clone with an
origin remote).
The Team plan covers 10 distinct repositories; the 11th+ scans at the free tier while your existing repos are unaffected. Free a slot via hello@pullguard.dev or upgrade to Enterprise for unlimited repos.
Not an error — it's the honesty line. Deep security (taint tracking), supply-chain/CVE, AI-era and compliance checks are partial or absent on the free tier, and category grades reflect only the checks that ran. A clean free-tier scan is not a security clean bill.
Large repositories can exceed the default scan caps. PullGuard never truncates silently: the report's
scan-coverage note states exactly how many collected files were never read, and
pullguard doctor predicts it before you scan. To widen coverage, raise the caps in
.driftrc.yml:
maxFiles: 10000 # collection cap (traversal)
maxFilesRead: 10000 # read cap — only read files are analyzed
maxDepth: 20 # directory depth
On a very large monorepo, scanning per-package usually beats one giant scan. Full reference: Configuration.
Very occasionally a single file — usually a generated, minified or otherwise pathological one — makes one analyzer take far longer than the rest of the repo put together. First, confirm it is one file: re-run with that path excluded.
exclude:
- "**/vendor/**/*.min.js" # the usual suspects: generated + minified files
If you would rather PullGuard handle it for you, turn on worker isolation. Each analyzer then runs in its own worker thread with a per-analyzer time limit: one that overruns is stopped, and the scan finishes with the rest of the results. It is opt-in because it is a different execution path — the findings are identical either way, and it costs a little start-up time per scan.
analyzerIsolation: worker # .driftrc.yml — or PULLGUARD_ANALYZER_ISOLATION=worker for one run
The per-analyzer limit defaults to 180 seconds. If an analyzer is stopped that you believe was doing real work
(very large repositories can legitimately need longer), raise it with PULLGUARD_ANALYZER_TIMEOUT_MS
— it is clamped to 5–600 seconds, so the limit can never be switched off entirely.
A stopped analyzer is never hidden: the report lists it under incompleteAnalyzers, the PR comment
carries the partial-scan banner, and the absence of its findings must not be read as "clean".
pullguard doctor tells you which mode a repo resolves to, and warns if worker was asked
for but is unavailable (in which case the scan runs inline).
Work through these in order — in our support history they explain nearly every case:
pullguard doctor shows the resolved tier and
analyzer count for the exact environment your scan uses.
PULLGUARD_OFFLINE=true is the one switch: every external call stays local (signed
embedded rule fallbacks, local CVE database, skipped freshness sweeps). Online
pg_live_ tokens cannot validate without network — air-gapped deployments use offline
keys instead. Details and the full egress disclosure:
Deployment & data sovereignty.
Windows checkouts (CRLF line endings) are first-class: our detection suites run every fixture under both LF and CRLF, so results do not differ by checkout platform.
The Action exposes three outputs per scan — score (0–100, lower is better),
grade (A–F), and findings (count) — usable in later workflow steps:
- name: PullGuard
id: pullguard
uses: pullguard-dev/pullguard-action@v1
- name: Use the result
run: echo "Grade ${{ steps.pullguard.outputs.grade }} (score ${{ steps.pullguard.outputs.score }}, ${{ steps.pullguard.outputs.findings }} findings)"
The full report lands in three places per run: the PR comment, the workflow's Step Summary, and the
pullguard-report artifact (self-contained HTML report + over-time dashboard + SARIF).
Findings past their configured age budget are flagged via the sla config block — see
Configuration.
Email hello@pullguard.dev — attaching the output of
pullguard doctor (keys are masked automatically) answers most questions in one round trip.