Your First Scan
Deep dive into interpreting your first Radar scan report, understanding violations, and strategies for reducing your initial debt score.
Your First Scan
Your first radar scan will likely produce a long list of violations. This is normal. Every codebase accumulates technical debt that is invisible without automated detection. The report Radar produces on the first run is internally called the "shock report" --- it reveals the true state of your architecture.
This guide explains every section of the scan output, walks through real violation examples, and gives you a concrete strategy for reducing your debt score incrementally.
Anatomy of a Scan Report
A full scan report has five sections:
Technical Debt Radar — Scan Report
══════════════════════════════════════════════════════════
Scanned 47 files in 2.3s ← 1. Header
ARCHITECTURE 4 violations ← 2. Category sections
──────────────────────────────────────────────────
BLOCK src/orders/domain/order.service.ts:3
layer-boundary-violation: ...
...
RUNTIME RISK 2 violations
──────────────────────────────────────────────────
...
PERFORMANCE 2 violations
──────────────────────────────────────────────────
...
RELIABILITY 3 violations
──────────────────────────────────────────────────
...
MAINTAINABILITY 1 violation
──────────────────────────────────────────────────
...
══════════════════════════════════════════════════════════
SUMMARY ← 3. Summary
Total violations: 12
Blocking: 7
Warnings: 5
Debt score: 58
GATE: ✗ FAIL — 7 blocking violations ← 4. Gate result
══════════════════════════════════════════════════════════
AI Summary (optional) ← 5. AI summary
This scan found 3 critical patterns: ...
1. Header
Shows the number of files scanned and the scan duration. Radar scans TypeScript (.ts, .tsx) and JavaScript (.js, .jsx) files. Configuration files, test files, and node_modules are excluded.
2. Category Sections
Violations are grouped by the six debt categories. Each violation includes:
- Severity tag ---
BLOCK(prevents merge) orWARN(advisory) - File path and line number --- Exact location:
src/orders/domain/order.service.ts:3 - Rule ID --- Machine-readable identifier:
layer-boundary-violation - Message --- Human-readable explanation of the violation
- Context --- Additional information such as the offending import, the handler scope, or the table size
3. Summary
Aggregate counts of violations by severity and the calculated debt score.
4. Gate Result
The final pass/fail decision. The gate blocks merge when any of these conditions are met:
| Condition | Threshold |
|---|---|
| Architecture violations | > 0 |
| Circular dependencies introduced | > 0 |
| Runtime risk (Critical severity) | > 0 |
| Reliability (Critical severity) | > 0 |
| Performance (Critical, on XL/XXL tables) | > 0 |
| Accumulated debt delta score | > 15 |
Note: The debt score threshold uses strict greater-than. A score of 15 passes; 16 blocks. Warning-level violations can accumulate past the threshold and block merges even without a single critical violation.
5. AI Summary (Optional)
When you have an ANTHROPIC_API_KEY set, Radar generates a prioritized summary after the deterministic scan. The AI reads all violations and produces a 2--3 sentence executive summary:
"This scan found 3 critical patterns: an unbounded query on the events table (1M+ rows) that will cause timeouts in production, a sync file read in the export handler that blocks the event loop for ~50ms under load, and a fire-and-forget email call that risks unhandled promise rejection crashes. The unbounded query is the highest priority."
Understanding Each Violation Type
Architecture Violations
Architecture violations are the most common findings on first scan. They mean your code imports across boundaries that your architecture preset forbids.
Layer boundary violation:
BLOCK src/orders/domain/order.service.ts:3
layer-boundary-violation: domain imports from infrastructure
import { PrismaClient } from '@prisma/client'
Your radar.yml declares a DDD architecture where the domain layer must not depend on infrastructure. The file order.service.ts lives in src/orders/domain/ (matched by the domain layer path), but it imports @prisma/client, which Radar maps to the infrastructure layer.
Why it matters: Domain logic coupled to a specific ORM cannot be tested in isolation and creates cascading changes when you switch database adapters.
How to fix: Introduce a repository interface in the domain layer and implement it in the infrastructure layer:
// src/orders/domain/order.repository.ts (interface)
export interface OrderRepository {
findById(id: string): Promise<Order | null>;
save(order: Order): Promise<void>;
}
// src/orders/infra/prisma-order.repository.ts (implementation)
import { PrismaClient } from '@prisma/client';
import { OrderRepository } from '../domain/order.repository';
export class PrismaOrderRepository implements OrderRepository {
constructor(private prisma: PrismaClient) {}
// ...
}
Circular dependency:
BLOCK src/orders/domain/order.entity.ts:2
circular-dependency: orders/domain ↔ billing/domain
Cycle: order.entity → billing.entity → order.entity
Radar builds a full import graph and runs Tarjan's algorithm to detect cycles. Bidirectional ORM entity imports (e.g., TypeORM relations between *.entity.ts files) are excluded because they are standard ORM patterns. Service-level and controller-level cycles are always flagged.
How to fix: Break the cycle by extracting a shared interface or using domain events:
// Instead of billing importing order.entity directly:
// Use an event or a shared interface in a contracts/ folder
import { OrderCreatedEvent } from '../../shared/contracts/events';
Module boundary violation:
BLOCK src/identity/controllers/user.controller.ts:4
module-boundary-violation: cross-module import not through contracts
import { OrderService } from '../../orders/application/order.service'
The identity module imports directly from orders. Your rules require cross-module imports to go through a contracts layer (e.g., src/**/contracts/**).
How to fix: Export a public API from the orders module through the contracts folder, and import that instead.
Runtime Risk Violations
Runtime violations are scope-aware. Radar only flags patterns inside request handlers (HTTP handlers, guards, interceptors, middleware). The same code in a cron job or startup script is not flagged.
Sync file system in handler:
BLOCK src/orders/controllers/export.controller.ts:18
sync-fs-in-handler: fs.readFileSync blocks the event loop
in @Get('export') handler — use fs.readFile (async) instead
fs.readFileSync blocks the Node.js event loop. In a request handler, this means every concurrent request waits while this one reads a file. Under load, this causes cascading timeouts.
How to fix: Replace with the async version:
// Before
const template = fs.readFileSync('./template.html', 'utf-8');
// After
const template = await fs.readFile('./template.html', 'utf-8');
Unbounded JSON parse:
WARN src/identity/controllers/auth.controller.ts:42
unbounded-json-parse: JSON.parse on request body without size limit
in @Post('login') handler — add express.json({ limit: '1mb' })
JSON.parse on large input can block the event loop for hundreds of milliseconds. This is a warning, not a blocker, but it becomes dangerous at scale.
Performance Violations
Performance violations scale with the data volumes you declare in radar.yml. The same query pattern gets different severity on different tables.
Unbounded findMany:
BLOCK src/orders/application/list-orders.use-case.ts:15
unbounded-find-many: prisma.order.findMany() without pagination
Table 'orders' is L (100K-1M rows) — add take/skip
prisma.order.findMany() without take or skip fetches all rows. On a table with 500K rows, this is a production incident.
Volume severity scale:
| Size | Rows | Behavior |
|---|---|---|
| S | < 10K | Suppressed (no violation) |
| M | 10K--100K | Warning |
| L | 100K--1M | Critical |
| XL | 1M--50M | Critical + Blocks merge |
| XXL | 50M+ | Critical + Blocks merge |
How to fix: Add pagination:
// Before
const orders = await prisma.order.findMany();
// After
const orders = await prisma.order.findMany({
take: 50,
skip: page * 50,
where: { status: 'active' },
});
N+1 query:
BLOCK src/billing/application/generate-report.ts:22
n-plus-one-query: query inside Promise.all loop
prisma.lineItem.findMany() called per order — use include or join
N+1 queries always block merges regardless of table size. A loop that runs 100 queries instead of 1 join is a performance bug at any scale.
How to fix: Use eager loading or a single query:
// Before (N+1: one query per order)
const orders = await prisma.order.findMany({ take: 50 });
for (const order of orders) {
order.lineItems = await prisma.lineItem.findMany({
where: { orderId: order.id },
});
}
// After (single query with include)
const orders = await prisma.order.findMany({
take: 50,
include: { lineItems: true },
});
Reliability Violations
Fire-and-forget promise:
BLOCK src/orders/application/send-notification.ts:8
unhandled-promise-rejection: fire-and-forget async call
emailService.send(user.email) — add await or .catch()
Calling an async function without await or .catch() means any rejection crashes the process with an unhandled promise rejection. In Node.js, this terminates the process by default.
How to fix:
// Before (fire-and-forget — process crash risk)
emailService.send(user.email);
// After — Option A: await it
await emailService.send(user.email);
// After — Option B: explicit error handling
emailService.send(user.email).catch((err) => {
logger.error('Failed to send notification', err);
});
Tip: In NestJS,
@Injectableservices with exception filters handle unhandled rejections for awaited calls automatically. But fire-and-forget calls (noawait) bypass exception filters entirely and are always flagged.
External call without timeout:
WARN src/billing/infra/stripe.adapter.ts:31
external-call-no-timeout: HTTP call without timeout
stripe.charges.create() — add timeout configuration
HTTP calls to external services can hang indefinitely without a timeout. If the external service is slow, your request handler holds a connection open, eventually exhausting the connection pool.
The "Copy for AI" Workflow
The fastest way to fix a large number of violations is the AI-to-AI feedback loop:
Step 1: Generate the AI-Friendly Report
radar scan . --format ai-prompt
This produces structured output designed for AI consumption:
Fix these issues in my codebase:
1. In src/orders/domain/order.service.ts line 3:
layer-boundary-violation: domain imports from infrastructure.
The file is in the domain layer but imports @prisma/client.
Fix: Create an OrderRepository interface in the domain layer
and move the Prisma implementation to infrastructure.
2. In src/orders/controllers/export.controller.ts line 18:
sync-fs-in-handler: fs.readFileSync blocks the event loop
in a @Get handler.
Fix: Replace with await fs.readFile() (async version).
3. In src/orders/application/list-orders.use-case.ts line 15:
unbounded-find-many: prisma.order.findMany() has no pagination.
Table 'orders' has L volume (100K-1M rows).
Fix: Add take and skip parameters.
[... remaining violations ...]
Step 2: Paste into Your AI Tool
Copy the entire output and paste it into Claude, Cursor, or your preferred AI coding assistant. The AI has exact file paths, line numbers, and fix instructions --- it can generate all fixes in one pass.
Step 3: Apply Fixes and Rescan
After the AI generates fixes, apply them and run Radar again:
radar scan .
The violations should be resolved. If any remain, repeat the loop. This typically takes 1--2 iterations to clear all violations.
Alternative: Use radar fix Directly
If you have an Anthropic API key, skip the copy-paste step entirely:
export ANTHROPIC_API_KEY=sk-ant-api03-...
radar fix .
Radar sends each violation to the Claude API, generates a fix, and applies it with your confirmation.
Strategies for Reducing Your Initial Score
A first scan on a mature codebase can surface dozens or even hundreds of violations. Do not try to fix everything at once. Use these strategies:
Strategy 1: Fix Blockers First, Ignore Warnings
Focus exclusively on violations with the BLOCK tag. These are the ones that will prevent merges once you enable Radar in CI:
radar scan . --format json | jq '[.violations[] | select(.gateAction == "block")] | length'
Strategy 2: Use Exceptions for Legacy Code
If a violation is in legacy code that you cannot safely change right now, add a time-boxed exception in radar.yml:
exceptions:
- rule: "domain -> infrastructure"
file: "src/billing/domain/legacy-adapter.ts"
expires: "2026-06-01"
reason: "Legacy migration — tracked in JIRA-1234"
Exceptions suppress the violation until the expiry date. After that, the violation reappears. This gives you a concrete deadline without blocking current development.
Strategy 3: Adjust Volume Declarations
If your data_volumes are set too aggressively, performance violations may over-fire. Review your actual table sizes:
data_volumes:
orders: L # Are you sure this is 100K-1M rows?
sessions: M # If it's actually < 10K, set to S to suppress
| Size | Rows | Effect on Performance Rules |
|---|---|---|
| S | < 10K | Suppressed entirely |
| M | 10K--100K | Warning only |
| L | 100K--1M | Critical |
| XL | 1M--50M | Critical + Blocks |
| XXL | 50M+ | Critical + Blocks |
Warning: Do not downgrade volumes just to suppress violations. If your
eventstable has 2M rows, setting it to S hides a real production risk. Be honest about your data --- Radar uses volumes to protect you from actual outages.
Strategy 4: Tackle One Category at a Time
Pick the category with the most violations and clear it completely before moving to the next:
- Architecture --- Fix layer violations and circular dependencies. These are usually mechanical (move imports, extract interfaces).
- Reliability --- Add
await, null guards, and error handling. Low-risk changes. - Runtime Risk --- Replace sync operations with async equivalents. Straightforward substitutions.
- Performance --- Add pagination and fix N+1 queries. Requires understanding the data model.
- Maintainability --- Refactor complex functions. Higher effort, lower urgency.
Strategy 5: Set a Reasonable Gate Threshold
If your current debt score is 120, you cannot immediately enforce a threshold of 15. Start with a higher threshold and ratchet down:
# Week 1: Allow existing debt, only block new debt
gates:
block_merge:
- debt_delta_score > 50
# Week 4: Tighten after major cleanup
gates:
block_merge:
- debt_delta_score > 25
# Week 8: Production threshold
gates:
block_merge:
- debt_delta_score > 15
Debt Score Calculation
Every violation contributes points to the debt score. The gate blocks when the total exceeds the threshold.
| Violation Type | Points |
|---|---|
| Architecture violation | +5 |
| Circular dependency | +10 |
| Event loop blocking (Critical) | +8 |
| Event loop warning | +3 |
| Performance risk (XL/XXL) | +8 |
| Performance risk (L/M) | +3 |
| N+1 query (any volume) | +8 |
| Unhandled promise rejection | +5 |
| Missing error handling / timeout | +3 |
| Complexity increase | +1 per point over threshold |
| AI-flagged concern | +2 |
Example calculation:
| Violation | Points |
|---|---|
| 2 architecture violations | 2 x 5 = 10 |
| 1 circular dependency | 1 x 10 = 10 |
| 1 sync-fs (critical) | 1 x 8 = 8 |
| 1 unbounded query (L table) | 1 x 3 = 3 |
| 1 N+1 query | 1 x 8 = 8 |
| 1 fire-and-forget | 1 x 5 = 5 |
| 2 missing timeouts | 2 x 3 = 6 |
| Total | 50 |
With a gate threshold of debt_delta_score > 15, this PR is blocked. The developer needs to fix enough violations to bring the score to 15 or below.
Output Formats
Use the format that fits your workflow:
| Format | Flag | Use Case |
|---|---|---|
| Text | --format text | Terminal reading (default) |
| JSON | --format json | CI pipelines, scripting, dashboards |
| Table | --format table | Compact overview |
| AI Prompt | --format ai-prompt | Paste into Claude/Cursor for AI fixes |
| AI JSON | --format ai-json | Structured input for AI tool integrations |
| PR | --format pr | Markdown for PR comments |
# Machine-readable for CI
radar scan . --format json | jq '.gateResult'
# Paste into AI for fixes
radar scan . --format ai-prompt | pbcopy
# Markdown for documentation
radar scan . --format pr > scan-report.md
Next Steps
- Configuration --- Customize layers, modules, volumes, and gate thresholds.
- Architecture Presets --- Deep dive into each of the 7 presets.
- Scoring Reference --- Full scoring formula and tuning options.
- CLI Reference --- Complete command documentation.