Thresholds
A threshold is the maximum number of findings a gate will tolerate before it fires. It gives you precise control over what counts as a failure and what is acceptable noise.
What a threshold does
When a scan completes, Gatekeep evaluates each gate in your contract:
- Filter findings to the gate's
category. - Keep only findings at or above the gate's
severity. - Count those findings.
- If the count exceeds
threshold, the gate fires.
A gate with "threshold": 0 fires as soon as a single qualifying finding exists. A gate with "threshold": 3 tolerates up to three qualifying findings before firing.
Default threshold
The default value is 0. If you omit threshold from a gate, any matching finding will cause the gate to fire. This is the strictest option and is appropriate for security gates in production services.
Choosing a threshold
Use threshold values to reflect the reality of your codebase and your team's capacity to act:
| Scenario | Recommended threshold | Reasoning |
|---|---|---|
| Zero-tolerance security gate on a new service | 0 |
Any critical or high security finding must block. |
| Security gate on an existing service with known tech debt | 2 or 3 |
Allows some pre-existing findings while blocking new ones during remediation. |
| Quality gate (complexity, type hints) in a large legacy codebase | 10 or higher |
Prevents the gate firing on every PR while the codebase is being improved incrementally. |
| Governance gate (missing README, no CI) | 0 |
These are binary conditions; one is too many. |
| License gate during a compliance audit | 0 |
Copyleft findings in a commercial repo require immediate review. |
Threshold examples
Zero threshold (strictest)
{
"category": "security",
"severity": "critical",
"threshold": 0,
"blocking": true
}
Fires immediately if any critical security finding is present. Blocks merge when "blocking": true.
Permissive threshold for a work-in-progress service
{
"category": "code_quality",
"severity": "low",
"threshold": 15,
"blocking": false,
"description": "Quality gate in progress; non-blocking while team addresses tech debt"
}
Fires only when quality findings exceed 15. Non-blocking, so the scan result is informational.
Graduated approach: warn early, block late
{
"gates": [
{
"category": "dependency",
"severity": "high",
"threshold": 0,
"blocking": true,
"description": "Block on any high or critical dependency CVE"
},
{
"category": "dependency",
"severity": "medium",
"threshold": 5,
"blocking": false,
"description": "Warn when medium CVEs accumulate"
}
]
}
Two gates on the same category with different severity and blocking settings. The first blocks on any high-or-above CVE. The second warns when medium CVEs reach five, without blocking.
Threshold and the blocking flag
The threshold field controls whether the gate fires. The blocking field controls what happens when it fires.
| Gate fires? | blocking |
Outcome |
|---|---|---|
| No | Either | Gate passes. No action required. |
| Yes | true |
Scan result is failed. CI integration exits non-zero. |
| Yes | false |
Gate fires but scan result is passed_with_warnings. CI integration exits zero. |
Start with "blocking": false when introducing a new gate to an existing service. Observe how often it fires over a few weeks, adjust the threshold to a realistic baseline, then switch to "blocking": true.
Reducing threshold over time
One effective pattern for addressing legacy tech debt is ratcheting: start with a high threshold that reflects the current state of the codebase, then lower it incrementally as findings are resolved. The contract in version control records your progress.
{
"description": "Ratchet: started at 20, target is 0 by end of Q3",
"category": "code_quality",
"severity": "medium",
"threshold": 8,
"blocking": true
}
Update threshold in each sprint as the team resolves findings. Merge the updated contract before reducing it so CI does not fail on the existing codebase.