Gate Rules
How Technical Debt Radar decides whether to block or pass a pull request, including the debt delta threshold, category-specific gates, and configuration options.
Gate Rules
Technical Debt Radar enforces merge gates on every pull request. A gate is a condition that must be satisfied for the PR to pass. If any gate condition fails, the PR status check is set to failure and the merge is blocked.
Gates operate at two levels: the debt delta score threshold and category-specific conditions that block independently of the score.
The Default Gate
Out of the box, Radar blocks a PR when any of the following conditions are true:
gates:
block_merge:
- architecture_violations > 0
- circular_dependencies_introduced > 0
- runtime_risk_critical > 0
- reliability_critical > 0
- critical_performance_risk > 0
- debt_delta_score > 15
warn:
- complexity_increase > 5
- coverage_drop > 2%
- debt_delta_score > 8
Block Conditions (PR Cannot Merge)
| Condition | Meaning |
|---|---|
architecture_violations > 0 | Any layer or module boundary violation blocks merge, regardless of score |
circular_dependencies_introduced > 0 | Any new circular dependency chain blocks merge |
runtime_risk_critical > 0 | Any critical event-loop blocker in a request handler blocks merge |
reliability_critical > 0 | Any unhandled promise rejection blocks merge |
critical_performance_risk > 0 | Any unbounded query on an XL/XXL table blocks merge |
debt_delta_score > 15 | Net debt increase above 15 points blocks merge |
Warn Conditions (PR Can Merge)
| Condition | Meaning |
|---|---|
complexity_increase > 5 | Warns if cyclomatic complexity increases by more than 5 |
coverage_drop > 2% | Warns if test coverage drops by more than 2 percentage points |
debt_delta_score > 8 | Warns when score is elevated but below the block threshold |
Key insight: A PR with a
debt_delta_scoreof 12 will pass the score gate (12 is not greater than 15) but will still be blocked if it contains even one architecture violation. Category-specific gates and the score gate are evaluated independently.
The Debt Delta Threshold
The debt_delta_score > 15 rule is the primary numerical gate. It works as follows:
- A score of 15 or below passes the gate.
- A score of 16 or above blocks the merge.
The threshold is intentionally set at 15 to allow small amounts of new debt while blocking PRs that introduce significant risk. A single architecture violation (+5) and a missing try/catch (+3) will not block on score alone, but three architecture violations (+15) plus a coverage drop (+3) will.
Why 15?
The threshold is calibrated so that a developer can introduce minor warnings in a PR without being blocked, but cannot introduce multiple serious issues in a single PR. At the default weights:
| Scenario | Score | Gate |
|---|---|---|
| 1 architecture violation + 1 reliability warning | 8 | Pass |
| 3 runtime risk warnings | 9 | Pass |
| 2 architecture violations + 1 performance warning | 13 | Pass |
| 3 architecture violations | 15 | Pass |
| 3 architecture violations + 1 dead code | 16 | Block |
| 1 circular dependency + 1 architecture violation | 15 | Block (arch gate) |
Notice that the last row is blocked by architecture_violations > 0 even though the score equals 15, which would pass the score gate. Category-specific gates always take precedence.
Category-Specific Gates
Five categories have independent block conditions that fire regardless of the debt delta score.
Architecture: Zero Tolerance
- architecture_violations > 0
- circular_dependencies_introduced > 0
Architecture violations always block because they compound exponentially. One layer violation is a 5-minute fix. After 50 files depend on the wrong abstraction, it is a multi-week refactoring project. The zero-tolerance policy prevents this drift entirely.
Circular dependencies always block because they prevent independent deployment, create initialization-order bugs, and make the codebase impossible to reason about in isolation.
Runtime Risk: Critical Only
- runtime_risk_critical > 0
Only critical runtime risks block. Warning-level runtime issues (like unbounded-json-parse) add to the score but do not independently block the merge. Critical means the pattern will block the event loop under production load --- fs.readFileSync, crypto.pbkdf2Sync, or a busy-wait loop inside a request handler.
Reliability: Critical Only
- reliability_critical > 0
Unhandled promise rejections block because they crash the Node.js process in --unhandled-rejections=throw mode (the default since Node 15). Other reliability issues like missing try/catch or empty catch blocks warn but do not independently block.
Performance: Volume-Aware
- critical_performance_risk > 0
Performance issues only block when they target XL (1M+ rows) or XXL (50M+ rows) tables. The same findMany() without pagination on a 5K-row table is a warning, but on a 2M-row table it blocks. This is controlled by the data_volumes section of your radar.yml.
Configuring Gate Thresholds
Override the default gates in the gates section of your radar.yml or rules.yml:
gates:
block_merge:
- architecture_violations > 0
- circular_dependencies_introduced > 0
- runtime_risk_critical > 0
- reliability_critical > 0
- critical_performance_risk > 0
- debt_delta_score > 20 # relaxed from 15 to 20
warn:
- complexity_increase > 10 # relaxed from 5 to 10
- coverage_drop > 5% # relaxed from 2% to 5%
- debt_delta_score > 12
Available Gate Metrics
| Metric | Type | Description |
|---|---|---|
architecture_violations | count | Layer/module boundary violations in changed files |
circular_dependencies_introduced | count | New circular dependency chains |
runtime_risk_critical | count | Critical event-loop blockers in handlers |
runtime_risk_warning | count | Warning-level runtime risks |
reliability_critical | count | Critical reliability violations |
reliability_warning | count | Warning-level reliability issues |
critical_performance_risk | count | Unbounded queries on XL/XXL tables |
debt_delta_score | number | Net debt score change |
complexity_increase | number | Cyclomatic complexity increase |
coverage_drop | percentage | Test coverage decrease |
maintainability_violations | count | Maintainability warnings |
Supported Operators
| Operator | Meaning |
|---|---|
> | Greater than |
>= | Greater than or equal |
< | Less than |
<= | Less than or equal |
== | Equal to |
Removing Category-Specific Gates
If you want architecture violations to add to the score but not independently block, remove the condition from block_merge:
gates:
block_merge:
# architecture_violations removed — now score-only
- circular_dependencies_introduced > 0
- runtime_risk_critical > 0
- reliability_critical > 0
- critical_performance_risk > 0
- debt_delta_score > 15
Warning: Removing
architecture_violations > 0from your gate config is strongly discouraged. Architecture drift is the single highest-cost form of technical debt because it compounds silently. Consider using exceptions for specific files instead.
Override Options
Per-File Exceptions
Exempt specific files from specific rules using the exceptions section:
exceptions:
- rule: "domain -> infrastructure"
file: "src/billing/domain/legacy-adapter.ts"
expires: "2026-06-01"
reason: "Legacy migration — tracked in JIRA-1234"
Exceptions require an expiration date and a reason. Expired exceptions are automatically ignored. This is the recommended way to handle legitimate violations during migrations or legacy integrations.
Relaxed Packs
Install a relaxed rule pack for gradual enforcement:
radar pack install nestjs-prisma-ddd-relaxed
Relaxed packs raise the debt_delta_score threshold, reduce some weights, and convert some block conditions to warnings. They are designed for teams adopting Radar on an existing codebase.
The --no-gate Flag
In the CLI, you can skip gate enforcement entirely:
radar scan . --no-gate
This runs the full analysis and outputs the report but always exits with code 0, regardless of violations. Useful during initial adoption when you want to see results without blocking merges.
CI vs CLI Gate Behavior
Gates behave slightly differently depending on where Radar is running.
In CI (GitHub Action / GitLab CI)
- Gate result is posted as a GitHub status check or GitLab commit status.
- A
failurestatus prevents merging when branch protection rules require the Radar check to pass. - The PR comment includes the full score breakdown and lists every violation.
- The
report-urloutput links to the full dashboard report.
In CLI (radar scan / radar run)
radar scanprints the report to stdout and exits with code 0. It does not enforce gates.radar runprints the report and exits with code 1 if any block condition is met. This is the CI-friendly command.- Both commands print the gate result at the bottom of the output:
Gate: PASS | Score: +8 (threshold: 15)
Gate: BLOCKED | Score: +26 (threshold: 15)
→ architecture_violations: 2 (threshold: 0)
→ debt_delta_score: 26 (threshold: 15)
In the Dashboard
The dashboard shows gate results for every PR scan with a visual timeline. Failed gates are highlighted in red, passed gates in green. The trend chart shows how your team's debt delta is changing over time.
Plan-Based Gate Behavior
Gate behavior varies by plan:
| Feature | Free | Solo | Team | Enterprise |
|---|---|---|---|---|
Score gate (debt_delta_score) | Default only (15) | Configurable | Configurable | Configurable |
| Category gates | Default only | Configurable | Configurable | Configurable |
| Custom gate metrics | No | No | Yes | Yes |
| Gate override via API | No | No | No | Yes |
| Exception management | In YAML only | In YAML only | YAML + Dashboard | YAML + Dashboard + API |
On the Free plan, the default gate configuration is enforced and cannot be changed. Upgrading to Solo or higher unlocks full gate customization.