Version Migration and Backward Compatibility for Claude Code Applications
This comprehensive guide covers version upgrade strategies, API versioning patterns, breaking change management, smooth migration workflows, backward compatibility testing, and feature flag strategies for gradual rollouts in Claude Code applications. Learn from real-world examples and best practices for maintaining stable, evolvable AI-powered applications.
Table of Contents
- Overview
- Semantic Versioning for Claude Code
- API Versioning Patterns
- Breaking Change Management
- Migration Workflows and Tooling
- Backward Compatibility Testing
- Feature Flag Strategies
- Prompt Version Management
- Real-World Examples
- Best Practices
Overview {#overview}
Version migration and backward compatibility are critical concerns for Claude Code applications due to:
- Non-deterministic AI behavior: Model updates can change output patterns
- Prompt sensitivity: Small prompt changes can have major impacts
- Integration complexity: Multiple APIs, SDKs, and services need coordination
- User expectations: Enterprise users require stability and predictable behavior
- Regulatory compliance: Some industries require version control and audit trails
Key Principles
- APIs are forever: Once published, assume your API will be used indefinitely
- Backward compatibility first: Breaking changes should be the exception, not the rule
- Communication is crucial: Give users ample notice and clear migration paths
- Test everything: Automated testing catches compatibility issues early
- Gradual rollouts: Use feature flags and canary deployments for safety
Semantic Versioning for Claude Code {#semantic-versioning}
Claude Code applications should follow Semantic Versioning (SemVer) principles:
Version Format: MAJOR.MINOR.PATCH
// package.json
{
"name": "@claude-code/sdk",
"version": "2.3.1",
"engines": {
"node": ">=18.0.0"
}
}Version Increment Rules
MAJOR (Breaking Changes)
Increment when you make incompatible API changes:
// Version 1.x.x
interface ClaudeResponse {
text: string;
confidence: number;
}
// Version 2.0.0 - Breaking change!
interface ClaudeResponse {
content: string; // renamed from 'text'
metadata: {
confidence: number; // moved to nested object
model: string;
};
}MINOR (New Features)
Increment when you add functionality in a backward-compatible manner:
// Version 2.3.x
interface ClaudeOptions {
model: string;
temperature?: number;
maxTokens?: number;
// New in 2.4.0 - optional parameter
tools?: ClaudeTool[];
}PATCH (Bug Fixes)
Increment when you make backward-compatible bug fixes:
// Version 2.3.0 - Bug in token counting
function countTokens(text: string): number {
return text.length; // Wrong!
}
// Version 2.3.1 - Fixed
function countTokens(text: string): number {
return encode(text).length; // Correct token counting
}Pre-release and Metadata
// Pre-release versions for testing
"version": "3.0.0-beta.1"
"version": "3.0.0-rc.2"
// Build metadata
"version": "3.0.0+build.123"API Versioning Patterns {#api-versioning-patterns}
1. URI/Path Versioning
Most common and explicit approach:
// Express.js API versioning
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);
// Version-specific route handlers
// v1/claude.routes.ts
router.post('/claude/prompt', async (req, res) => {
const { text, model } = req.body;
// v1 logic
});
// v2/claude.routes.ts
router.post('/claude/prompt', async (req, res) => {
const { content, model, tools } = req.body;
// v2 logic with new features
});2. Header-Based Versioning
Clean URLs with version in headers:
// Middleware to handle version headers
function versionMiddleware(req: Request, res: Response, next: NextFunction) {
const version = req.headers['x-api-version'] || '1.0';
// Route to appropriate handler
switch(version) {
case '1.0':
req.apiVersion = 'v1';
break;
case '2.0':
req.apiVersion = 'v2';
break;
default:
return res.status(400).json({
error: 'Unsupported API version'
});
}
next();
}
// Usage
app.use(versionMiddleware);
app.post('/api/claude/prompt', (req, res) => {
const handler = handlers[req.apiVersion];
return handler(req, res);
});3. Content-Type Versioning
Using Accept headers for version negotiation:
// Accept: application/vnd.claudecode.v2+json
app.post('/api/claude/prompt', (req, res) => {
const acceptHeader = req.headers.accept;
const versionMatch = acceptHeader?.match(/vnd\.claudecode\.v(\d+)/);
const version = versionMatch ? versionMatch[1] : '1';
// Handle based on version
});4. GraphQL Schema Versioning
Evolution without explicit versions:
# Deprecated field approach
type ClaudeResponse {
text: String @deprecated(reason: "Use 'content' instead")
content: String
confidence: Float @deprecated(reason: "Use 'metadata.confidence'")
metadata: ResponseMetadata
}
# New types for new versions
type ClaudeResponseV2 {
content: String!
metadata: ResponseMetadata!
tools: [ToolResult!]
}
type Query {
# Old endpoint
claudePrompt(input: String!): ClaudeResponse
# New endpoint
claudePromptV2(input: ClaudeInput!): ClaudeResponseV2
}Breaking Change Management {#breaking-change-management}
Identifying Breaking Changes
// Breaking change detector utility
export class BreakingChangeDetector {
private changes: BreakingChange[] = [];
analyzeAPIChanges(oldSchema: APISchema, newSchema: APISchema) {
// Check removed endpoints
for (const endpoint of oldSchema.endpoints) {
if (!newSchema.endpoints.find(e => e.path === endpoint.path)) {
this.changes.push({
type: 'ENDPOINT_REMOVED',
severity: 'CRITICAL',
path: endpoint.path,
message: `Endpoint ${endpoint.path} has been removed`
});
}
}
// Check parameter changes
for (const endpoint of oldSchema.endpoints) {
const newEndpoint = newSchema.endpoints.find(e => e.path === endpoint.path);
if (newEndpoint) {
this.checkParameterChanges(endpoint, newEndpoint);
}
}
// Check response changes
this.checkResponseChanges(oldSchema, newSchema);
return this.changes;
}
private checkParameterChanges(oldEndpoint: Endpoint, newEndpoint: Endpoint) {
// Required parameters added
const newRequired = newEndpoint.parameters
.filter(p => p.required)
.filter(p => !oldEndpoint.parameters.find(op => op.name === p.name));
if (newRequired.length > 0) {
this.changes.push({
type: 'REQUIRED_PARAMETER_ADDED',
severity: 'HIGH',
path: oldEndpoint.path,
parameters: newRequired.map(p => p.name),
message: `New required parameters: ${newRequired.map(p => p.name).join(', ')}`
});
}
// Parameter type changes
for (const oldParam of oldEndpoint.parameters) {
const newParam = newEndpoint.parameters.find(p => p.name === oldParam.name);
if (newParam && newParam.type !== oldParam.type) {
this.changes.push({
type: 'PARAMETER_TYPE_CHANGED',
severity: 'HIGH',
path: oldEndpoint.path,
parameter: oldParam.name,
oldType: oldParam.type,
newType: newParam.type
});
}
}
}
}Deprecation Strategy
// Deprecation decorator for TypeScript
function deprecated(message: string, removeInVersion?: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.warn(
`DEPRECATION WARNING: ${propertyKey} is deprecated. ${message}` +
(removeInVersion ? ` Will be removed in version ${removeInVersion}.` : '')
);
// Track deprecation usage
trackDeprecatedUsage({
method: propertyKey,
timestamp: new Date(),
caller: new Error().stack
});
return originalMethod.apply(this, args);
};
return descriptor;
};
}
// Usage
class ClaudeAPI {
@deprecated('Use promptV2() instead', '3.0.0')
async prompt(text: string): Promise<string> {
// Old implementation
return this.promptV2({ content: text }).then(r => r.content);
}
async promptV2(input: ClaudeInput): Promise<ClaudeResponse> {
// New implementation
}
}Sunset Timeline
// API sunset configuration
export const API_SUNSET_SCHEDULE = {
v1: {
deprecationDate: '2024-01-01',
sunsetWarningDate: '2024-06-01',
sunsetDate: '2024-12-31',
migrationGuide: 'https://docs.claudecode.com/migration/v1-to-v2'
}
};
// Middleware to add sunset headers
function sunsetMiddleware(version: string) {
return (req: Request, res: Response, next: NextFunction) => {
const sunset = API_SUNSET_SCHEDULE[version];
if (sunset) {
const now = new Date();
const sunsetDate = new Date(sunset.sunsetDate);
// Add deprecation headers
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', sunsetDate.toUTCString());
res.setHeader('Link', `<${sunset.migrationGuide}>; rel="sunset"`)
// Warning period
if (now >= new Date(sunset.sunsetWarningDate)) {
res.setHeader('Warning', '299 - "API version will be sunset soon"');
}
}
next();
};
}Migration Workflows and Tooling {#migration-workflows}
Automated Migration Tool
// Claude Code Migration CLI
export class ClaudeCodeMigrator {
private migrations: Map<string, Migration> = new Map();
registerMigration(fromVersion: string, toVersion: string, migration: Migration) {
const key = `${fromVersion}->${toVersion}`;
this.migrations.set(key, migration);
}
async migrate(
currentVersion: string,
targetVersion: string,
options: MigrationOptions
): Promise<MigrationResult> {
const path = this.findMigrationPath(currentVersion, targetVersion);
if (!path) {
throw new Error(`No migration path from ${currentVersion} to ${targetVersion}`);
}
const results: StepResult[] = [];
// Backup current state
if (options.backup) {
await this.createBackup(options.projectPath);
}
// Execute migrations in sequence
for (const step of path) {
try {
console.log(`Migrating from ${step.from} to ${step.to}...`);
const migration = this.migrations.get(`${step.from}->${step.to}`);
if (!migration) {
throw new Error(`Migration not found: ${step.from} -> ${step.to}`);
}
const result = await migration.execute({
projectPath: options.projectPath,
dryRun: options.dryRun,
verbose: options.verbose
});
results.push(result);
if (result.errors.length > 0 && !options.continueOnError) {
throw new Error('Migration failed with errors');
}
} catch (error) {
if (options.rollbackOnError) {
await this.rollback(results);
}
throw error;
}
}
return {
success: true,
fromVersion: currentVersion,
toVersion: targetVersion,
steps: results
};
}
private findMigrationPath(from: string, to: string): MigrationStep[] {
// Use graph algorithm to find shortest migration path
// Implementation details...
}
}
// Migration implementation example
export class MigrationV1ToV2 implements Migration {
async execute(options: MigrationExecutionOptions): Promise<StepResult> {
const files = await this.findFiles(options.projectPath, '**/*.ts');
const changes: Change[] = [];
const errors: Error[] = [];
for (const file of files) {
try {
const content = await fs.readFile(file, 'utf-8');
const ast = parseTypeScript(content);
// Transform AST
const transformed = this.transformAST(ast);
if (transformed.hasChanges) {
const newContent = generateCode(transformed.ast);
if (!options.dryRun) {
await fs.writeFile(file, newContent);
}
changes.push({
file,
type: 'CODE_TRANSFORMATION',
description: transformed.changes
});
}
} catch (error) {
errors.push({
file,
error: error.message
});
}
}
// Update configuration files
await this.updateConfigs(options);
// Update dependencies
await this.updateDependencies(options);
return {
changes,
errors,
warnings: this.collectWarnings()
};
}
private transformAST(ast: AST): TransformResult {
// Transform old API calls to new format
return transformSync(ast, {
visitors: {
CallExpression(path) {
if (path.node.callee.name === 'claudePrompt') {
// Transform claudePrompt(text) to claudePromptV2({ content: text })
path.replaceWith(
t.callExpression(
t.identifier('claudePromptV2'),
[t.objectExpression([
t.objectProperty(
t.identifier('content'),
path.node.arguments[0]
)
])]
)
);
}
}
}
});
}
}Interactive Migration Assistant
// AI-powered migration assistant
export class ClaudeMigrationAssistant {
private anthropic: Anthropic;
async analyzeMigrationImpact(
projectPath: string,
fromVersion: string,
toVersion: string
): Promise<MigrationAnalysis> {
// Scan codebase
const codebaseAnalysis = await this.scanCodebase(projectPath);
// Get breaking changes
const breakingChanges = await this.getBreakingChanges(fromVersion, toVersion);
// Use Claude to analyze impact
const prompt = `
Analyze the migration impact for a Claude Code application:
Current Version: ${fromVersion}
Target Version: ${toVersion}
Breaking Changes:
${JSON.stringify(breakingChanges, null, 2)}
Codebase Summary:
- Total files: ${codebaseAnalysis.fileCount}
- API calls found: ${codebaseAnalysis.apiCalls.length}
- Dependencies: ${JSON.stringify(codebaseAnalysis.dependencies)}
Please provide:
1. Estimated effort (hours)
2. Risk assessment (low/medium/high)
3. Specific files that need changes
4. Recommended migration strategy
5. Potential issues to watch for
`;
const response = await this.anthropic.messages.create({
messages: [{ role: 'user', content: prompt }],
model: 'claude-3-opus-20240229',
max_tokens: 2000
});
return this.parseAnalysisResponse(response.content[0].text);
}
async generateMigrationPlan(
analysis: MigrationAnalysis
): Promise<MigrationPlan> {
const plan: MigrationPlan = {
phases: [],
estimatedDuration: analysis.estimatedEffort,
riskLevel: analysis.riskAssessment
};
// Phase 1: Preparation
plan.phases.push({
name: 'Preparation',
tasks: [
{
title: 'Create full backup',
description: 'Backup code, configurations, and data',
automated: true,
script: 'npm run backup:full'
},
{
title: 'Update dependencies',
description: 'Update Claude Code SDK to latest compatible version',
automated: true,
script: 'npm update @claude-code/sdk'
},
{
title: 'Run compatibility tests',
description: 'Execute test suite to establish baseline',
automated: true,
script: 'npm run test:compat'
}
]
});
// Phase 2: Code Migration
plan.phases.push({
name: 'Code Migration',
tasks: analysis.requiredChanges.map(change => ({
title: `Update ${change.file}`,
description: change.description,
automated: change.automatable,
script: change.automatable ? `npm run migrate:file ${change.file}` : undefined,
manualSteps: change.manualSteps
}))
});
// Phase 3: Testing
plan.phases.push({
name: 'Testing & Validation',
tasks: [
{
title: 'Run unit tests',
automated: true,
script: 'npm test'
},
{
title: 'Run integration tests',
automated: true,
script: 'npm run test:integration'
},
{
title: 'Perform compatibility testing',
automated: false,
manualSteps: [
'Test with sample of production data',
'Verify API responses match expected format',
'Check performance metrics'
]
}
]
});
return plan;
}
}Backward Compatibility Testing {#compatibility-testing}
Automated Compatibility Test Suite
// Compatibility test framework
export class CompatibilityTestRunner {
private oldClient: ClaudeClientV1;
private newClient: ClaudeClientV2;
async runCompatibilityTests(): Promise<TestResults> {
const results = new TestResults();
// Test 1: Response format compatibility
await this.testResponseCompatibility(results);
// Test 2: Error handling compatibility
await this.testErrorCompatibility(results);
// Test 3: Performance regression
await this.testPerformanceRegression(results);
// Test 4: Feature parity
await this.testFeatureParity(results);
return results;
}
private async testResponseCompatibility(results: TestResults) {
const testCases = [
{
name: 'Simple prompt',
input: 'Hello, Claude',
validate: (v1: any, v2: any) => {
// V2 should be able to provide V1-compatible response
return v2.content === v1.text;
}
},
{
name: 'Complex prompt with tools',
input: {
prompt: 'Analyze this data',
tools: ['calculator', 'web_search']
},
validate: (v1: any, v2: any) => {
// Check backward compatibility adapter
const v1Compatible = v2.toV1Format();
return deepEqual(v1Compatible, v1);
}
}
];
for (const testCase of testCases) {
try {
const v1Response = await this.oldClient.prompt(testCase.input);
const v2Response = await this.newClient.prompt(testCase.input);
if (testCase.validate(v1Response, v2Response)) {
results.addPass(testCase.name);
} else {
results.addFailure(testCase.name, 'Response format incompatible');
}
} catch (error) {
results.addError(testCase.name, error);
}
}
}
private async testPerformanceRegression(results: TestResults) {
const iterations = 100;
const testPrompt = 'Generate a short story about AI';
// Benchmark V1
const v1Times: number[] = [];
for (let i = 0; i < iterations; i++) {
const start = performance.now();
await this.oldClient.prompt(testPrompt);
v1Times.push(performance.now() - start);
}
// Benchmark V2
const v2Times: number[] = [];
for (let i = 0; i < iterations; i++) {
const start = performance.now();
await this.newClient.prompt({ content: testPrompt });
v2Times.push(performance.now() - start);
}
const v1Avg = average(v1Times);
const v2Avg = average(v2Times);
const regression = ((v2Avg - v1Avg) / v1Avg) * 100;
if (regression > 10) {
results.addWarning(
'Performance Regression',
`V2 is ${regression.toFixed(2)}% slower than V1`
);
} else {
results.addPass('Performance Test');
}
}
}
// Contract testing
export class ContractTest {
async validateContract(
endpoint: string,
version: string,
contract: APIContract
): Promise<ValidationResult> {
const response = await fetch(endpoint, {
method: contract.method,
headers: {
'X-API-Version': version,
'Content-Type': 'application/json'
},
body: JSON.stringify(contract.request)
});
const responseData = await response.json();
// Validate response schema
const schemaValidation = validateSchema(responseData, contract.responseSchema);
// Validate business rules
const businessValidation = await this.validateBusinessRules(
responseData,
contract.businessRules
);
return {
passed: schemaValidation.valid && businessValidation.valid,
schemaErrors: schemaValidation.errors,
businessErrors: businessValidation.errors
};
}
}Regression Test Suite
// Regression test for prompt outputs
export class PromptRegressionTester {
private testCases: Map<string, PromptTestCase> = new Map();
async runRegressionTests(
client: ClaudeClient,
options: RegressionOptions
): Promise<RegressionResults> {
const results = new RegressionResults();
for (const [id, testCase] of this.testCases) {
try {
const response = await client.prompt(testCase.input);
const similarity = await this.calculateSimilarity(
response,
testCase.expectedOutput
);
if (similarity < options.threshold) {
results.addRegression({
testId: id,
similarity,
expected: testCase.expectedOutput,
actual: response,
severity: this.calculateSeverity(similarity, testCase.importance)
});
} else {
results.addPass(id);
}
} catch (error) {
results.addError(id, error);
}
}
return results;
}
private async calculateSimilarity(
actual: string,
expected: string
): Promise<number> {
// Use embeddings for semantic similarity
const [actualEmbedding, expectedEmbedding] = await Promise.all([
this.getEmbedding(actual),
this.getEmbedding(expected)
]);
return cosineSimilarity(actualEmbedding, expectedEmbedding);
}
}Feature Flag Strategies {#feature-flags}
Feature Flag Implementation
// Feature flag service for Claude Code
export class ClaudeFeatureFlags {
private provider: FeatureFlagProvider;
private cache: Map<string, boolean> = new Map();
constructor(provider: FeatureFlagProvider) {
this.provider = provider;
}
async isEnabled(
flag: string,
context?: FeatureContext
): Promise<boolean> {
const cacheKey = `${flag}:${context?.userId || 'default'}`;
// Check cache first
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!;
}
// Evaluate flag
const enabled = await this.provider.evaluate(flag, {
userId: context?.userId,
attributes: context?.attributes,
percentage: context?.percentage
});
// Cache result
this.cache.set(cacheKey, enabled);
return enabled;
}
// Gradual rollout helper
async gradualRollout(
flag: string,
schedule: RolloutSchedule
): Promise<void> {
for (const phase of schedule.phases) {
await this.provider.update(flag, {
percentage: phase.percentage,
segments: phase.segments
});
// Wait for phase duration
await sleep(phase.duration);
// Check metrics
const metrics = await this.getMetrics(flag);
if (metrics.errorRate > phase.errorThreshold) {
// Rollback
await this.provider.update(flag, {
percentage: 0
});
throw new Error(`Rollout failed at ${phase.percentage}%`);
}
}
}
}
// Usage in Claude Code application
export class ClaudeService {
private featureFlags: ClaudeFeatureFlags;
private v1Client: ClaudeClientV1;
private v2Client: ClaudeClientV2;
async prompt(input: string, userId?: string): Promise<any> {
// Check if user should use new version
const useV2 = await this.featureFlags.isEnabled('claude-v2-api', {
userId,
attributes: {
plan: 'enterprise',
region: 'us-east-1'
}
});
if (useV2) {
// Use new API with compatibility wrapper
const response = await this.v2Client.prompt({ content: input });
return this.wrapV2Response(response);
} else {
// Use old API
return this.v1Client.prompt(input);
}
}
private wrapV2Response(v2Response: V2Response): V1Response {
// Adapter pattern for backward compatibility
return {
text: v2Response.content,
confidence: v2Response.metadata.confidence,
// Map other fields...
};
}
}Canary Deployment with Claude Code
// Canary deployment configuration
export class ClaudeCanaryDeployment {
private loadBalancer: LoadBalancer;
private monitoring: MonitoringService;
async deployCanary(
version: string,
config: CanaryConfig
): Promise<DeploymentResult> {
// Step 1: Deploy new version to canary instances
await this.deployVersion(version, config.canaryInstances);
// Step 2: Configure initial traffic split
await this.loadBalancer.updateWeights({
stable: 100 - config.initialPercentage,
canary: config.initialPercentage
});
// Step 3: Progressive rollout
for (const step of config.rolloutSteps) {
await sleep(step.duration);
// Check health metrics
const health = await this.checkCanaryHealth();
if (!health.isHealthy) {
await this.rollback();
return {
success: false,
reason: health.issues
};
}
// Increase traffic
await this.loadBalancer.updateWeights({
stable: 100 - step.percentage,
canary: step.percentage
});
// Log progress
console.log(`Canary at ${step.percentage}%: ${health.metrics}`);
}
// Step 4: Full rollout
await this.promoteCanary();
return { success: true };
}
private async checkCanaryHealth(): Promise<HealthCheck> {
const metrics = await this.monitoring.getMetrics({
services: ['claude-stable', 'claude-canary'],
duration: '5m'
});
const stableErrorRate = metrics['claude-stable'].errorRate;
const canaryErrorRate = metrics['claude-canary'].errorRate;
// Compare error rates
const errorRateIncrease = (canaryErrorRate - stableErrorRate) / stableErrorRate;
// Check latency
const stableP99 = metrics['claude-stable'].latencyP99;
const canaryP99 = metrics['claude-canary'].latencyP99;
const latencyIncrease = (canaryP99 - stableP99) / stableP99;
return {
isHealthy: errorRateIncrease < 0.05 && latencyIncrease < 0.10,
issues: [],
metrics: {
errorRateIncrease,
latencyIncrease,
canaryTraffic: metrics['claude-canary'].requestCount
}
};
}
}A/B Testing Framework
// A/B testing for Claude Code features
export class ClaudeABTesting {
private experiments: Map<string, Experiment> = new Map();
async runExperiment(
name: string,
variants: Variant[],
config: ExperimentConfig
): Promise<ExperimentResults> {
const experiment = new Experiment({
name,
variants,
allocation: config.allocation,
duration: config.duration,
metrics: config.metrics
});
this.experiments.set(name, experiment);
// Wait for experiment to complete
await sleep(config.duration);
// Analyze results
return this.analyzeExperiment(experiment);
}
async getVariant(
experimentName: string,
userId: string
): Promise<string> {
const experiment = this.experiments.get(experimentName);
if (!experiment) {
return 'control';
}
// Consistent hash for user assignment
const hash = await this.hash(userId + experimentName);
const bucket = hash % 100;
let cumulative = 0;
for (const variant of experiment.variants) {
cumulative += variant.percentage;
if (bucket < cumulative) {
return variant.name;
}
}
return 'control';
}
// Usage example
async promptWithABTest(
input: string,
userId: string
): Promise<ClaudeResponse> {
const variant = await this.getVariant('prompt-enhancement-v2', userId);
switch (variant) {
case 'enhanced-context':
return this.promptWithEnhancedContext(input);
case 'multi-step-reasoning':
return this.promptWithMultiStepReasoning(input);
default:
return this.standardPrompt(input);
}
}
}Prompt Version Management {#prompt-versioning}
Prompt Versioning System
// Prompt version management for Claude Code
export class PromptVersionManager {
private storage: PromptStorage;
private cache: Map<string, PromptVersion> = new Map();
async savePrompt(
key: string,
content: string,
metadata?: PromptMetadata
): Promise<PromptVersion> {
const version = new PromptVersion({
key,
content,
version: await this.getNextVersion(key),
timestamp: new Date(),
author: metadata?.author || 'system',
tags: metadata?.tags || [],
performance: await this.benchmarkPrompt(content)
});
await this.storage.save(version);
this.cache.set(`${key}:${version.version}`, version);
return version;
}
async getPrompt(
key: string,
version?: string
): Promise<PromptVersion> {
const cacheKey = `${key}:${version || 'latest'}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!;
}
const prompt = version
? await this.storage.getVersion(key, version)
: await this.storage.getLatest(key);
this.cache.set(cacheKey, prompt);
return prompt;
}
async compareVersions(
key: string,
v1: string,
v2: string
): Promise<VersionComparison> {
const [version1, version2] = await Promise.all([
this.getPrompt(key, v1),
this.getPrompt(key, v2)
]);
// Calculate differences
const textDiff = this.diffText(version1.content, version2.content);
// Compare performance
const perfDiff = {
latency: version2.performance.avgLatency - version1.performance.avgLatency,
tokenUsage: version2.performance.avgTokens - version1.performance.avgTokens,
successRate: version2.performance.successRate - version1.performance.successRate
};
// Test both versions
const testResults = await this.runComparisonTests(version1, version2);
return {
textChanges: textDiff,
performanceChanges: perfDiff,
testResults,
recommendation: this.generateRecommendation(perfDiff, testResults)
};
}
async rollback(key: string, toVersion: string): Promise<void> {
const version = await this.getPrompt(key, toVersion);
await this.savePrompt(key, version.content, {
author: 'system',
tags: ['rollback', `from-${await this.getCurrentVersion(key)}`]
});
}
}
// Prompt template with variable substitution
export class PromptTemplate {
private template: string;
private variables: Set<string>;
private validators: Map<string, (value: any) => boolean> = new Map();
constructor(template: string) {
this.template = template;
this.variables = this.extractVariables(template);
}
render(context: Record<string, any>): string {
// Validate all required variables are present
for (const variable of this.variables) {
if (!(variable in context)) {
throw new Error(`Missing required variable: ${variable}`);
}
// Run validators
const validator = this.validators.get(variable);
if (validator && !validator(context[variable])) {
throw new Error(`Invalid value for variable: ${variable}`);
}
}
// Substitute variables
return this.template.replace(
/\{\{(\w+)\}\}/g,
(match, variable) => context[variable]
);
}
addValidator(variable: string, validator: (value: any) => boolean) {
this.validators.set(variable, validator);
}
private extractVariables(template: string): Set<string> {
const matches = template.matchAll(/\{\{(\w+)\}\}/g);
return new Set(Array.from(matches, m => m[1]));
}
}Prompt Performance Tracking
// Track prompt performance across versions
export class PromptPerformanceTracker {
private metrics: MetricsCollector;
async trackExecution(
promptKey: string,
version: string,
execution: PromptExecution
): Promise<void> {
const metrics = {
promptKey,
version,
duration: execution.endTime - execution.startTime,
tokensUsed: execution.tokensUsed,
success: execution.success,
errorType: execution.error?.type,
userId: execution.userId,
timestamp: new Date()
};
await this.metrics.record('prompt_execution', metrics);
// Update aggregate metrics
await this.updateAggregates(promptKey, version, metrics);
}
async getPerformanceReport(
promptKey: string,
timeRange: TimeRange
): Promise<PerformanceReport> {
const data = await this.metrics.query({
metric: 'prompt_execution',
filter: { promptKey },
timeRange
});
// Group by version
const versionMetrics = new Map<string, VersionMetrics>();
for (const record of data) {
const version = record.version;
if (!versionMetrics.has(version)) {
versionMetrics.set(version, new VersionMetrics());
}
const metrics = versionMetrics.get(version)!;
metrics.addExecution(record);
}
// Generate report
return {
promptKey,
timeRange,
versions: Array.from(versionMetrics.entries()).map(([version, metrics]) => ({
version,
executions: metrics.count,
avgDuration: metrics.avgDuration,
p95Duration: metrics.p95Duration,
successRate: metrics.successRate,
avgTokensUsed: metrics.avgTokens,
errors: metrics.errorBreakdown
})),
recommendation: this.generateRecommendation(versionMetrics)
};
}
private generateRecommendation(
versionMetrics: Map<string, VersionMetrics>
): string {
// Find best performing version
let bestVersion = '';
let bestScore = -Infinity;
for (const [version, metrics] of versionMetrics) {
// Scoring algorithm weighing different factors
const score =
metrics.successRate * 100 +
(1 / metrics.avgDuration) * 10 +
(1 / metrics.avgTokens) * 5;
if (score > bestScore) {
bestScore = score;
bestVersion = version;
}
}
return `Recommend using version ${bestVersion} based on performance metrics`;
}
}Real-World Examples {#real-world-examples}
Example 1: Stripe-Style API Versioning
// Stripe-inspired date-based versioning for Claude Code
export class ClaudeDateVersioning {
private versions: Map<string, APIVersion> = new Map();
constructor() {
// Register API versions
this.versions.set('2024-01-15', new APIVersion2024_01_15());
this.versions.set('2024-06-30', new APIVersion2024_06_30());
this.versions.set('2024-12-01', new APIVersion2024_12_01());
}
async handleRequest(
request: Request,
response: Response
): Promise<void> {
// Get version from header or account setting
const version = request.headers['claude-version'] ||
await this.getAccountVersion(request.user);
const apiVersion = this.versions.get(version) ||
this.versions.get('2024-12-01'); // Latest
// Add version to response headers
response.setHeader('Claude-Version', apiVersion.version);
// Process request with appropriate version
await apiVersion.processRequest(request, response);
}
}
// Version-specific implementations
class APIVersion2024_06_30 extends BaseAPIVersion {
async processPrompt(input: any): Promise<any> {
// Version-specific logic
if (typeof input === 'string') {
// Old format - single string
return this.claude.prompt(input);
} else {
// New format introduced in this version
return this.claude.promptWithOptions(input);
}
}
}Example 2: Salesforce-Style Multi-Version Support
// Supporting multiple API versions simultaneously
export class ClaudeMultiVersionAPI {
private readonly SUPPORTED_VERSIONS = [
'v58.0', 'v59.0', 'v60.0', 'v61.0', 'v62.0' // Latest
];
private readonly VERSION_LIFECYCLE = {
'v58.0': { status: 'deprecated', sunsetDate: '2025-03-01' },
'v59.0': { status: 'deprecated', sunsetDate: '2025-06-01' },
'v60.0': { status: 'supported' },
'v61.0': { status: 'supported' },
'v62.0': { status: 'current' }
};
async routeRequest(
version: string,
endpoint: string,
request: any
): Promise<any> {
if (!this.SUPPORTED_VERSIONS.includes(version)) {
throw new APIError(`Unsupported version: ${version}`, 400);
}
const lifecycle = this.VERSION_LIFECYCLE[version];
if (lifecycle.status === 'deprecated') {
console.warn(
`API version ${version} is deprecated and will be removed on ${lifecycle.sunsetDate}`
);
}
// Route to version-specific handler
const handler = this.getHandler(version, endpoint);
return handler(request);
}
}Example 3: GitHub-Style Breaking Change Previews
// Preview API changes before they become breaking
export class ClaudePreviewAPI {
async handleRequest(
request: Request,
response: Response
): Promise<void> {
const acceptHeader = request.headers.accept;
const previews = this.parsePreviewHeaders(acceptHeader);
// Enable preview features based on header
const features = {
enhancedTools: previews.includes('enhanced-tools-preview'),
streamingv2: previews.includes('streaming-v2-preview'),
batchAPI: previews.includes('batch-api-preview')
};
// Process with preview features
const result = await this.processWithFeatures(request, features);
// Add preview headers to response
if (previews.length > 0) {
response.setHeader('X-Claude-Previews', previews.join(', '));
response.setHeader(
'Warning',
'299 - "Preview features are subject to change"'
);
}
response.json(result);
}
private parsePreviewHeaders(acceptHeader: string): string[] {
// Parse Accept: application/vnd.claude.v3+json; preview=enhanced-tools,streaming-v2
const match = acceptHeader.match(/preview=([^;]+)/);
return match ? match[1].split(',').map(p => p.trim()) : [];
}
}Best Practices {#best-practices}
1. Version Planning
// Version roadmap configuration
export const VERSION_ROADMAP = {
'2.0.0': {
releaseDate: '2024-06-01',
features: ['New prompt API', 'Tool support', 'Streaming v2'],
breakingChanges: ['Renamed response fields', 'New auth method'],
migrationGuide: '/docs/migration/v1-to-v2'
},
'2.1.0': {
releaseDate: '2024-09-01',
features: ['Batch API', 'Enhanced context window'],
breakingChanges: [],
migrationGuide: null
},
'3.0.0': {
releaseDate: '2025-01-01',
features: ['Multi-modal support', 'Agent framework'],
breakingChanges: ['Complete API redesign'],
migrationGuide: '/docs/migration/v2-to-v3'
}
};2. Communication Strategy
// Automated deprecation notices
export class DeprecationNotifier {
async notifyUsers(deprecation: Deprecation): Promise<void> {
// Email notification
await this.emailService.sendBulk({
template: 'api-deprecation',
recipients: await this.getAffectedUsers(deprecation),
data: {
version: deprecation.version,
feature: deprecation.feature,
sunsetDate: deprecation.sunsetDate,
migrationGuide: deprecation.migrationGuide,
alternativeAPI: deprecation.alternative
}
});
// In-app notification
await this.notificationService.createBanner({
type: 'warning',
message: `API ${deprecation.feature} will be deprecated on ${deprecation.sunsetDate}`,
ctaText: 'View Migration Guide',
ctaLink: deprecation.migrationGuide,
targetUsers: await this.getAffectedUsers(deprecation)
});
// API response headers
this.addDeprecationHeaders(deprecation);
}
}3. Testing Strategy
// Comprehensive version testing
export class VersionTestStrategy {
async testVersionMigration(
fromVersion: string,
toVersion: string
): Promise<TestReport> {
const tests = [
// Unit tests
this.runUnitTests(toVersion),
// Integration tests
this.runIntegrationTests(fromVersion, toVersion),
// Compatibility tests
this.runCompatibilityTests(fromVersion, toVersion),
// Performance tests
this.runPerformanceTests(fromVersion, toVersion),
// Security tests
this.runSecurityTests(toVersion),
// Load tests
this.runLoadTests(toVersion)
];
const results = await Promise.all(tests);
return {
fromVersion,
toVersion,
results,
passed: results.every(r => r.passed),
report: this.generateReport(results)
};
}
}4. Monitoring Strategy
// Version-aware monitoring
export class VersionMonitoring {
async trackVersionUsage(): Promise<UsageReport> {
const metrics = await this.metricsService.query({
metric: 'api_requests',
groupBy: ['version', 'endpoint'],
timeRange: 'last_30_days'
});
return {
versionDistribution: this.calculateDistribution(metrics),
deprecatedVersionUsage: this.getDeprecatedUsage(metrics),
migrationProgress: this.calculateMigrationProgress(metrics),
recommendations: this.generateRecommendations(metrics)
};
}
async alertOnVersionIssues(): Promise<void> {
// Alert if deprecated version usage increases
const deprecatedUsage = await this.getDeprecatedVersionUsage();
if (deprecatedUsage.trend === 'increasing') {
await this.alertService.send({
severity: 'warning',
title: 'Increasing deprecated API usage',
message: `Deprecated version ${deprecatedUsage.version} usage increased by ${deprecatedUsage.increase}%`
});
}
// Alert on version-specific errors
const errorRates = await this.getVersionErrorRates();
for (const [version, rate] of errorRates) {
if (rate > 0.05) { // 5% error threshold
await this.alertService.send({
severity: 'critical',
title: `High error rate for version ${version}`,
message: `Error rate: ${rate * 100}%`
});
}
}
}
}Conclusion
Version migration and backward compatibility are critical for maintaining stable Claude Code applications while enabling innovation. Key takeaways:
- Plan versions carefully - Use semantic versioning and plan breaking changes well in advance
- Communicate extensively - Over-communicate deprecations and provide clear migration paths
- Test thoroughly - Automated testing catches compatibility issues before users do
- Roll out gradually - Use feature flags and canary deployments to minimize risk
- Monitor continuously - Track version usage and migration progress
- Support gracefully - Maintain old versions during reasonable transition periods
By following these patterns and best practices, you can evolve your Claude Code applications while maintaining user trust and system stability.
Related Resources
- Claude Code TypeScript SDK
- CD Integration Patterns
- Comprehensive Testing Guide
- Monitoring and Observability
- Advanced API Integration Patterns
External References
- Semantic Versioning Specification
- API Versioning Best Practices
- Feature Flag Best Practices
- Canary Deployment Strategies
- API Evolution Without Breaking Changes
Last Updated: 2025-07-22 Status: Production-Ready Patterns