Fixing Violations
How to reduce your technical debt score by fixing violations, earning credits, and using AI-assisted fixes to unblock pull requests.
Fixing Violations
Every fixed violation earns a negative credit that reduces your debt delta score. A PR that fixes more than it introduces will have a negative delta --- actively reducing the technical debt in your codebase.
This page covers how fix credits work, which violations to fix first, and how to use radar fix for AI-assisted resolution.
Fix Credits
When Radar detects that a violation present in the base branch has been removed in the PR, it applies a fix credit:
| Fix Type | Credit | What It Means |
|---|---|---|
| Architecture violation fixed | -5 | Removed a layer or module boundary violation |
| Runtime risk fixed | -8 | Removed a critical event-loop blocker |
| Reliability issue fixed | -3 | Added error handling, timeout, or null guard |
| Complexity reduced | -1 per unit | Reduced cyclomatic complexity of a function |
Credits are calculated automatically. Radar compares the current scan results against the stored baseline for each changed file. If a violation existed before and is gone now, the credit is applied.
Example
Your PR modifies src/orders/domain/order.service.ts. The baseline had two violations:
PrismaClientimported directly in domain layer (+5 architecture)- Missing try/catch in
createOrdermethod (+3 reliability)
Your PR fixes both and introduces one new warning:
- Code duplication with another file (+2 maintainability)
debt_delta_score = (+2) + (-5) + (-3) = -6
The PR reduces total debt by 6 points. It passes the gate and your codebase is healthier.
Prioritization Strategy
Not all violations are equal. Fix high-point violations first to maximize your score improvement per hour of work.
Tier 1: Fix These First (8--10 Points Each)
Circular dependencies (+10). These are the most expensive violations. They prevent independent deployment, create initialization-order bugs, and make the codebase impossible to reason about. Fixing one circular dependency removes 10 points from your debt.
How to fix: Extract shared types into a contracts/ or shared/ directory. Replace direct imports with dependency injection or event-based communication.
Critical runtime risks (+8). Event-loop blockers in request handlers cause production timeouts. Each fix earns an 8-point credit.
How to fix: Replace fs.readFileSync with fs.promises.readFile. Replace crypto.pbkdf2Sync with crypto.pbkdf2 (callback) or util.promisify(crypto.pbkdf2). Move CPU-intensive work to a worker thread or background job.
Critical performance risks (+8). Unbounded queries on large tables will cause outages as data grows.
How to fix: Add take and skip parameters to every findMany call on XL/XXL tables. Add where clauses to narrow results. Convert N+1 patterns to include (Prisma) or eager loading (Sequelize/TypeORM).
Tier 2: Fix Next (5 Points Each)
Architecture violations (+5). Layer and module boundary violations are quick to fix individually but expensive if left to accumulate.
How to fix: Move the import to the correct layer. If a domain service needs database access, inject a repository interface instead of importing the ORM directly. If cross-module access is needed, expose a contract in src/**/contracts/**.
Unhandled promise rejections (+5). These crash the process in strict mode.
How to fix: Add await inside a try/catch block, or add .catch() to the promise chain. Never fire-and-forget a promise without explicit error handling.
Tier 3: Fix When Convenient (1--3 Points Each)
Missing error handling (+3). Add try/catch to async operations, timeouts to HTTP calls, and null guards after nullable queries.
Runtime risk warnings (+3). Address unbounded-json-parse, large-json-stringify, and similar patterns when touching those files.
Code duplication (+2). Extract shared logic into utility functions or shared modules.
Dead code (+1). Remove unused exports and unreachable code.
Category-by-Category Fix Guide
Architecture
| Violation | Fix |
|---|---|
| Domain imports infrastructure | Inject a repository interface; move ORM calls to infra layer |
| Domain imports API | Extract shared types to a contracts directory |
| API imports infrastructure directly | Route through application/use-case layer |
| Cross-module direct import | Expose a contract or use events for cross-module communication |
// BEFORE: domain importing infrastructure
// src/orders/domain/order.service.ts
import { PrismaClient } from '@prisma/client'; // violation
// AFTER: domain depends on interface
// src/orders/domain/order.service.ts
import { OrderRepository } from '../contracts/order.repository';
export class OrderService {
constructor(private readonly repo: OrderRepository) {}
}
Runtime Risk
| Violation | Fix |
|---|---|
fs.readFileSync in handler | Use fs.promises.readFile with await |
crypto.pbkdf2Sync | Use crypto.pbkdf2 with callback or promisify |
JSON.parse on unbounded input | Validate input size before parsing, or stream-parse |
| Busy-wait loop | Use setTimeout, setInterval, or event-based patterns |
// BEFORE: sync file read in handler
@Get('template')
getTemplate() {
return fs.readFileSync('./template.html', 'utf-8'); // blocks event loop
}
// AFTER: async file read
@Get('template')
async getTemplate() {
return fs.promises.readFile('./template.html', 'utf-8');
}
Performance
| Violation | Fix |
|---|---|
Unbounded findMany() on XL table | Add take and skip parameters |
| N+1 query pattern | Use include for eager loading |
| Raw SQL without LIMIT | Add LIMIT clause |
| Missing pagination | Implement cursor-based or offset pagination |
// BEFORE: unbounded query on XL table
const events = await prisma.event.findMany(); // 2M+ rows
// AFTER: paginated query
const events = await prisma.event.findMany({
take: 100,
skip: page * 100,
where: { createdAt: { gte: startDate } },
orderBy: { createdAt: 'desc' },
});
Reliability
| Violation | Fix |
|---|---|
| Fire-and-forget promise | Add await in try/catch or .catch() |
| Missing try/catch | Wrap async operations in try/catch with typed errors |
| HTTP call without timeout | Add timeout option to HTTP client config |
| Empty catch block | Log the error and rethrow or handle appropriately |
// BEFORE: fire-and-forget
sendNotification(user.email);
// AFTER: awaited with error handling
try {
await sendNotification(user.email);
} catch (error) {
this.logger.error('Failed to send notification', { userId: user.id, error });
}
Maintainability
| Violation | Fix |
|---|---|
| High cyclomatic complexity | Extract conditional logic into named helper functions |
| Code duplication | Extract shared logic into utility functions |
| Dead code | Remove unused exports and unreachable branches |
| Coverage drop | Add tests for the changed code paths |
Using radar fix for AI-Assisted Fixes
The radar fix command uses the Claude API to generate code patches for detected violations. It analyzes each violation in context, generates a fix, and presents it for your review before applying.
# Fix all violations in the current directory
radar fix .
# Fix violations in a specific path
radar fix src/orders/
# Fix only architecture violations
radar fix . --category architecture
# Dry run — show fixes without applying
radar fix . --dry-run
How It Works
- Radar scans the target path and identifies all violations.
- For each violation, Radar sends the surrounding code context (the violating function plus imports and related types) to the Claude API.
- Claude generates a minimal diff that resolves the violation without changing behavior.
- Radar presents the diff in your terminal with syntax highlighting.
- You choose to apply, skip, or modify each fix.
$ radar fix src/orders/domain/order.service.ts
Found 2 violations in src/orders/domain/order.service.ts
[1/2] Architecture: domain → infrastructure (line 3)
PrismaClient imported directly in domain layer
Suggested fix:
- import { PrismaClient } from '@prisma/client';
+ import { OrderRepository } from '../contracts/order.repository';
Apply this fix? [y/n/e(dit)]
Note:
radar fixrequires theANTHROPIC_API_KEYenvironment variable and a Solo plan or higher.
Fix Options
| Flag | Description |
|---|---|
--category <name> | Fix only violations in the specified category |
--dry-run | Show fixes without applying them |
--auto-apply | Apply all fixes without prompting (use with caution) |
--max-fixes <n> | Limit the number of fixes per run |
Tracking Progress Over Time
The Technical Debt Radar dashboard shows your debt score trend over time, making it easy to track whether your team is reducing or accumulating debt.
Dashboard Trend Chart
The trend chart on the dashboard plots your total debt score after each PR scan. A downward trend means your team is consistently fixing more than it introduces.
Key metrics visible on the dashboard:
- Total debt score --- cumulative score across all files in the repository
- Debt delta per PR --- average net change per pull request
- Fix rate --- percentage of PRs that reduce debt (negative delta)
- Category breakdown --- which categories are contributing the most debt
- Top violating files --- files with the highest concentration of violations
Setting Team Goals
Use the trend data to set actionable goals:
- Maintenance goal: Keep the average debt delta per PR at zero or below. Every PR should fix at least as much as it introduces.
- Reduction goal: Reduce total debt score by a target percentage per sprint. Allocate cleanup PRs that focus on high-point violations.
- Prevention goal: Maintain zero architecture violations across all PRs. The
architecture_violations > 0gate enforces this automatically.
Cleanup Sprints
Dedicate periodic sprints to debt reduction. Focus on Tier 1 violations (circular dependencies, critical runtime risks, critical performance risks) for maximum impact. A single cleanup PR that resolves two circular dependencies earns 20 points of credit.
# Find the highest-scoring violations to prioritize
radar scan . --sort-by score --limit 20
This outputs the top 20 violations sorted by point value, giving you a prioritized hit list for your cleanup sprint.