Detect and warn about breaking API/schema changes before implementation. Auto-trigger when modifying API routes, database schemas, or public interfaces.
Breaking changes destroy trust and break client applications:
This skill acts as a safety gate that:
The result: Zero unintentional breaking changes, smooth API evolution, maintained client trust.
API modifications:
Database modifications:
Interface modifications:
Step 2: Analyze breaking change risk
Step 3: Determine severity
Step 4: Suggest safe migration path
Step 5: Validate against api-strategy.md
Step 6: Block or warn
With breaking-change-detector:
Developer: "Let me remove this old /api/users endpoint"
Detector: "🚨 BREAKING CHANGE DETECTED
Removing: GET /api/users
Severity: CRITICAL
Impact: 15 active clients (iOS app, Android app, web dashboard)
This will break:
- Mobile app v2.1-2.5 (500K users)
- Partner integrations (3 companies)
Safe migration path:
1. Deprecate endpoint (add X-Deprecated header)
2. Announce removal timeline (90 days)
3. Monitor usage (track who's still calling it)
4. Version API (v2 without endpoint, v1 keeps it)
5. Remove after 90 days when usage drops to 0
Cannot proceed without migration plan. Would you like me to implement the deprecation strategy?"
Developer: "Yes, let's deprecate it properly"
Result: Smooth transition, zero broken clients, maintained trust
Parse user request to identify change type:
API endpoint changes:
Database schema changes:
Interface/contract changes:
See references/change-detection-patterns.md for complete patterns.
Categorize the detected change:
Type 1: Hard breaking changes (always break clients):
Type 2: Soft breaking changes (may break some clients):
Type 3: Non-breaking changes (safe):
See references/breaking-change-taxonomy.md for full classification.
Determine who/what will be affected:
For API changes:
Find endpoint usage:
Estimate client count:
Determine criticality:
For database changes:
Find column usage:
Assess data impact:
For interface changes:
Find function usage:
Determine coupling:
Assign severity based on impact:
CRITICAL (blocks deployment):
HIGH (requires approval):
MEDIUM (requires documentation):
LOW (informational):
Read project's API versioning policy from docs/project/api-strategy.md:
Common strategies:
URL versioning: /api/v1/users, /api/v2/users
Header versioning: Accept: application/vnd.app.v1+json
No versioning (deprecation only):
Example api-strategy.md:
# API Versioning Strategy
**Strategy**: URL versioning (/api/v1, /api/v2)
**Breaking change policy**:
- Major version bump required (v1 → v2)
- Old version supported for 6 months minimum
- Deprecation notice 90 days before removal
- Monitor usage, remove when <1% traffic
**Backward compatibility requirements**:
- New fields are optional
- Removed fields are deprecated first (90 days)
- Response format changes require new version
If api-strategy.md doesn't exist, use conservative defaults:
Provide options based on change type and versioning strategy:
For endpoint removal:
Option A: Deprecation strategy
1. Add X-Deprecated header to endpoint
2. Add warning to API docs
3. Email clients (if known)
4. Monitor usage for 90 days
5. Remove when usage drops to 0
Option B: API versioning
1. Keep endpoint in /api/v1
2. Remove from /api/v2
3. Redirect v1 → v2 replacement (if exists)
4. Sunset v1 after 6 months
Option C: Soft delete
1. Return 410 Gone instead of 404
2. Include migration instructions in response
3. Log attempts for monitoring
For schema changes:
Option A: Additive migration
1. Add new column (don't drop old)
2. Dual-write to both columns
3. Migrate existing data
4. Update code to use new column
5. Drop old column in future version
Option B: Versioned schema
1. Create new table (users_v2)
2. Dual-write to both tables
3. Migrate readers to new table
4. Drop old table after migration
Option C: Nullable transition
1. Make column nullable (don't drop)
2. Update code to handle null
3. Backfill default values
4. Make required again if needed
For interface changes:
Option A: Deprecate + replace
1. Mark function as @deprecated
2. Create new function with new signature
3. Update call sites gradually
4. Remove deprecated function in major version
Option B: Overload
1. Add new overload with new signature
2. Keep old overload functional
3. Route to implementation
4. Remove old overload later
Option C: Default parameters
1. Add new parameters with defaults
2. Backward compatible (old calls still work)
3. No breaking change
See references/migration-strategies.md for complete playbook.
Based on severity, take action:
CRITICAL → BLOCK:
🚨 BREAKING CHANGE BLOCKED
Cannot proceed with this change without migration plan.
Change: Remove GET /api/users
Severity: CRITICAL
Impact: 500K users (mobile app), 3 partner integrations
Required before proceeding:
[ ] Migration plan documented
[ ] Stakeholders notified
[ ] Deprecation timeline agreed
[ ] Monitoring in place
[ ] Rollback plan ready
Would you like me to implement the recommended deprecation strategy?
HIGH → WARN:
⚠️ BREAKING CHANGE DETECTED
This change will break existing clients.
Change: Add required field "country" to POST /api/users
Severity: HIGH
Impact: Web app, mobile apps will fail validation
Recommended migration:
1. Make field optional initially
2. Update all clients to send field
3. Make field required in v2.0
Proceed anyway? [y/N]
MEDIUM → LOG:
ℹ️ Breaking change detected (MEDIUM severity)
Change: Change validation error format
Impact: Clients parsing error messages
Document this change in:
- CHANGELOG.md
- API migration guide
- Release notes
Proceeding with implementation...
// File deletions routes/.ts deleted → Endpoint removal controllers/.ts deleted → Controller removal
**Modification detection**:
```typescript
// Request schema changes
"interface.*Request.*{" (before/after diff)
"@Body.*:" (parameter changes)
"validate.*schema" (validation changes)
// Response schema changes
"interface.*Response.*{" (before/after diff)
"res.json({" (response format changes)
// HTTP method changes
"router.get" → "router.post" (method change)
Examples:
// BREAKING: Endpoint removal
- router.get('/api/users/:id', ...) // Removed
// Severity: CRITICAL
// BREAKING: Required field added
interface CreateUserRequest {
name: string;
email: string;
+ country: string; // NEW REQUIRED FIELD
}
// Severity: HIGH
// BREAKING: Response field removed
interface UserResponse {
id: string;
name: string;
- email: string; // REMOVED
}
// Severity: HIGH
-- SQL migration ALTER TABLE users DROP COLUMN email; // Severity: CRITICAL (data loss)
**Type changes**:
```sql
-- Changing column type
ALTER TABLE users ALTER COLUMN age TYPE varchar; // was integer
// Severity: HIGH (data conversion required)
Constraint changes:
-- Adding NOT NULL to existing column
ALTER TABLE users ALTER COLUMN email SET NOT NULL;
// Severity: HIGH (existing nulls will fail)
-- Removing constraint
ALTER TABLE users DROP CONSTRAINT users_email_unique;
// Severity: MEDIUM (behavior change)
Table changes:
DROP TABLE users;
// Severity: CRITICAL (data loss + code breaks)
ALTER TABLE users RENAME TO customers;
// Severity: CRITICAL (all queries break)
// Parameter type change
// Return type change
**Method removal**:
```typescript
class UserService {
- async deleteUser(id: string) { } // Removed
}
// Severity: HIGH (callers break)
Required property addition:
interface Config {
apiKey: string;
+ environment: string; // NEW REQUIRED
}
// Severity: HIGH (existing configs invalid)
// Adding auth to public endpoint
**Permission changes**:
```typescript
// Changing required role
- @RequireRole('user')
+ @RequireRole('admin')
// Severity: HIGH (users lose access)
Code pattern triggers:
User intent triggers:
Explicitly marked safe:
**/internal/** (not public API)// SAFE: backward compatibleDuring code review: Before creating PR/commit:
Detection:
Change type: API endpoint removal
File: routes/stats.ts
Line: router.get('/api/v1/stats', ...)
Action: DELETE
Impact analysis:
# Search for endpoint usage
grep -r "/api/v1/stats" --include="*.ts" --include="*.js"
Found:
- Frontend: src/services/api.ts (1 call)
- Mobile app: Documented in integration guide
- API logs: 450 requests/day
Clients:
- Web dashboard (internal, can update)
- Mobile app v2.3 (external, 10K users)
- Unknown clients (logged as "curl/7.0", "Python requests")
Severity determination:
Traffic: 450 requests/day → Moderate usage
Clients: External mobile app → Cannot force update
Unknown clients: Present → Unpredictable impact
Severity: CRITICAL
Reason: External clients depend on endpoint
Migration suggestion:
🚨 BREAKING CHANGE BLOCKED
Cannot remove /api/v1/stats - active external usage detected.
Impact:
- Mobile app v2.3 (10K users)
- Unknown clients (curl, Python scripts)
- 450 requests/day
Safe migration path (RECOMMENDED):
Phase 1: Deprecation (Days 0-90)
1. Add response header: X-Deprecated: "true; sunset=2025-03-01; replacement=/api/v2/analytics"
2. Add deprecation notice to API docs
3. Email known clients (mobile team)
4. Log warnings when endpoint called
Phase 2: Monitoring (Days 0-90)
1. Track usage metrics
2. Identify remaining clients (log User-Agent)
3. Reach out to high-volume clients
Phase 3: Removal (Day 90+)
1. Wait until usage < 10 requests/day
2. Return 410 Gone with migration instructions
3. Monitor for spikes (indicates missed client)
4. Remove code after 30 days at 410
Estimated timeline: 4 months
Would you like me to implement Phase 1 (deprecation)?
Implementation (if approved):
// routes/stats.ts (DEPRECATE, don't remove)
router.get('/api/v1/stats',
deprecationMiddleware({
sunset: '2025-03-01',
replacement: '/api/v2/analytics',
message: 'This endpoint is deprecated and will be removed on 2025-03-01'
}),
statsController.getStats // Keep working
);
// middleware/deprecation.ts (NEW)
export function deprecationMiddleware(options: DeprecationOptions) {
return (req, res, next) => {
res.header('X-Deprecated', 'true');
res.header('Sunset', options.sunset);
res.header('Link', `<${options.replacement}>; rel="successor-version"`);
logger.warn('Deprecated endpoint called', {
endpoint: req.path,
client: req.get('User-Agent'),
ip: req.ip,
sunset: options.sunset
});
next();
};
}
See examples/api-endpoint-removal-full.md for complete example.
Detection:
Change type: Database column removal
Table: users
Column: phone
Action: DROP COLUMN
Impact analysis:
# Search for column usage
grep -r "\.phone\|phone:" --include="*.ts"
Found:
- models/User.ts: phone: string
- services/UserService.ts: user.phone (3 references)
- controllers/UserController.ts: req.body.phone (1 reference)
- Database: 15,000 users have phone numbers (non-null)
Data at risk: 15,000 phone numbers
Code impact: 5 files, 10 references
Severity determination:
Data loss: 15,000 records → CRITICAL
Code breakage: 5 files → HIGH
Rollback difficulty: Cannot recover deleted data → CRITICAL
Severity: CRITICAL
Reason: Irreversible data loss
Migration suggestion:
🚨 BREAKING CHANGE BLOCKED
Cannot drop users.phone column - data loss risk.
Impact:
- 15,000 users have phone numbers (data loss)
- 5 files reference phone column (code breaks)
- No backup of phone data
Safe migration path (RECOMMENDED):
Option A: Soft delete (SAFEST)
1. Rename column: phone → phone_deprecated
2. Make column nullable
3. Update code to not use phone
4. Keep data for 6 months
5. Drop column after verification period
Option B: Archive before delete
1. Export phone data to archive table
2. Create users_phone_archive(user_id, phone, archived_at)
3. Copy data: INSERT INTO users_phone_archive SELECT id, phone FROM users
4. Verify archive: Check row counts match
5. Drop column from users table
6. Keep archive table indefinitely
Option C: Make nullable (don't drop)
1. ALTER TABLE users ALTER COLUMN phone DROP NOT NULL
2. Update code to handle null
3. Set existing phones to null (or keep them)
4. Column exists but unused (no data loss)
Which option do you prefer? I recommend Option B (archive).
See examples/database-column-removal.md for complete example.
Detection:
Change type: Function signature modification
Function: getUserById
Return type change: Promise<User> → Promise<User | null>
Impact analysis:
// Search for function calls
grep -r "getUserById" --include="*.ts"
Found 23 call sites:
- controllers/UserController.ts (3 calls)
- services/OrderService.ts (5 calls)
- services/NotificationService.ts (2 calls)
- ... (13 more files)
None handle null return:
const user = await getUserById(id);
user.name // Will throw if null
Severity determination:
Call sites: 23 locations
Null handling: 0/23 handle null
Impact: Runtime errors (Cannot read 'name' of null)
Severity: HIGH
Reason: Breaks existing code with runtime errors
Migration suggestion:
⚠️ BREAKING CHANGE DETECTED
Changing getUserById return type to nullable breaks 23 call sites.
Current: Promise<User> (never null)
Proposed: Promise<User | null> (can be null)
Impact:
- 23 call sites assume non-null
- Will throw "Cannot read property of null" at runtime
- Requires updating all callers
Safe migration path:
Option A: Deprecate + replace (RECOMMENDED)
1. Keep getUserById as-is (throws if not found)
2. Create findUserById that returns User | null
3. Update callers gradually to findUserById
4. Mark getUserById as @deprecated
5. Remove getUserById in v2.0
Option B: Update all callers first
1. Find all 23 call sites
2. Add null checks: if (!user) throw new NotFoundError()
3. Then change return type
4. Runtime-safe, but large changeset
Option C: Overload (TypeScript only)
1. Add overload: getUserById(id, options: { throwIfNotFound: false }): Promise<User | null>
2. Keep default: getUserById(id): Promise<User>
3. Backward compatible
Recommended: Option A (new function)
Implementation (Option A):
// services/UserService.ts
/**
* Get user by ID
* @deprecated Use findUserById instead. This will be removed in v2.0.
* @throws NotFoundError if user doesn't exist
*/
async getUserById(id: string): Promise<User> {
const user = await this.findUserById(id);
if (!user) {
throw new NotFoundError(`User ${id} not found`);
}
return user;
}
/**
* Find user by ID
* @returns User if found, null otherwise
*/
async findUserById(id: string): Promise<User | null> {
return await prisma.user.findUnique({ where: { id } });
}
Bad approach:
Detector: "⚠️ This will break clients"
Developer: "It's fine, I'll fix it later" *proceeds anyway*
Result: Production breaks, emergency rollback
Correct approach:
Detector: "⚠️ This will break clients"
Developer: "Let me implement the migration path first"
*Implements deprecation strategy*
*Then makes change safely*
Rule: NEVER ignore CRITICAL warnings. HIGH warnings require approval. MEDIUM warnings require documentation.
Bad approach:
-- Just drop it
DROP TABLE old_users;
Correct approach:
-- Deprecate first
ALTER TABLE old_users RENAME TO old_users_deprecated;
-- Add deprecation notice
COMMENT ON TABLE old_users_deprecated IS 'DEPRECATED: Use users table instead. Will be dropped 2025-06-01';
-- Monitor usage (if any queries fail, we know what's still using it)
-- Drop after deprecation period
DROP TABLE old_users_deprecated; -- 6 months later
Rule: Deprecate first, remove later (after monitoring period).
Bad approach:
// v1 returns array
GET /api/users → [{ id: 1 }, { id: 2 }]
// Changed to pagination (BREAKING)
GET /api/users → { items: [...], total: 2, page: 1 }
Correct approach:
// v1 unchanged
GET /api/v1/users → [{ id: 1 }, { id: 2 }]
// v2 with pagination
GET /api/v2/users → { items: [...], total: 2, page: 1 }
Rule: Breaking changes require version bump (or deprecation path if not versioned).
Bad approach:
ALTER TABLE users DROP COLUMN legacy_id; -- Data gone forever
Correct approach:
-- Archive first
CREATE TABLE users_legacy_data AS
SELECT id, legacy_id FROM users;
-- Then drop
ALTER TABLE users DROP COLUMN legacy_id;
-- Keep archive for 12 months
Rule: Archive before delete. Data recovery is impossible after DROP.
Detection metrics:
Prevention metrics:
Migration metrics:
CRITICAL severity:
HIGH severity:
MEDIUM severity:
Change detection: references/change-detection-patterns.md
Breaking change taxonomy: references/breaking-change-taxonomy.md
Migration strategies: references/migration-strategies.md
Integration with workflow: references/workflow-integration.md