Practical n8n and Claude Code Workflows
This guide provides production-ready workflow patterns and real-world implementation examples for combining n8n with Claude Code. Each example includes complete implementations, best practices, and lessons learned from production deployments.
π― Who Is This Guide For?
This guide is for developers with intermediate-to-advanced experience using n8n, Docker, and Node.js. It assumes you are comfortable with concepts like webhooks, REST APIs, and database management. If you are new to these topics, we recommend starting with the n8n Integration Overview first.
πΊοΈ System Architecture Overview
The workflows in this guide rely on several interconnected services. The diagram below illustrates how they work together. The n8n instance orchestrates the workflows, calling out to a dedicated, production-ready Claude API service that you will also create.
graph TD subgraph "External Triggers" A[GitHub Webhook] --> B(n8n); C[Jira Webhook] --> B; end subgraph "n8n Automation Platform" B -- Executes Workflow --> D{Custom Logic}; D -- Queues Jobs --> E[Redis]; D -- Stores State --> F[Postgres DB]; D -- Makes API Call --> G(Custom Claude API Service); end subgraph "Custom API Service (Node.js)" G -- Securely Calls --> H[Anthropic API]; end style G fill:#f9f,stroke:#333,stroke-width:2px style B fill:#9f9,stroke:#333,stroke-width:2px
π― Production Setup
Infrastructure Requirements
# docker-compose.production.yml
version: '3.8'
services:
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "5678:5678"
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=${N8N_USER}
- N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- DB_TYPE=postgresdb
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- EXECUTIONS_PROCESS=queue
- QUEUE_BULL_REDIS_HOST=redis
- N8N_METRICS=true
volumes:
- n8n_data:/home/node/.n8n
depends_on:
- postgres
- redis
networks:
- n8n_network
postgres:
image: postgres:15
restart: always
environment:
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=n8n
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- n8n_network
redis:
image: redis:7-alpine
restart: always
volumes:
- redis_data:/data
networks:
- n8n_network
claude-api:
build: ./claude-api
restart: always
ports:
- "3000:3000"
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- NODE_ENV=production
- LOG_LEVEL=info
- RATE_LIMIT_REQUESTS=100
- RATE_LIMIT_WINDOW=60000
volumes:
- ./projects:/app/projects
networks:
- n8n_network
volumes:
n8n_data:
postgres_data:
redis_data:
networks:
n8n_network:
driver: bridgeProduction Claude API Service
// claude-api/src/server.ts
import express from 'express';
import { Anthropic } from '@anthropic-ai/sdk';
import { RateLimiter } from './rateLimiter';
import { MetricsCollector } from './metrics';
import { SecurityMiddleware } from './security';
import { CacheManager } from './cache';
const app = express();
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
// Production middleware
app.use(express.json({ limit: '10mb' }));
app.use(SecurityMiddleware.validateRequest);
app.use(SecurityMiddleware.sanitizeInput);
app.use(RateLimiter.middleware);
app.use(MetricsCollector.middleware);
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
version: process.env.npm_package_version,
uptime: process.uptime()
});
});
// Main Claude execution endpoint
app.post('/execute', async (req, res) => {
const requestId = req.headers['x-request-id'] || crypto.randomUUID();
const startTime = Date.now();
try {
const { prompt, context, mode, options = {} } = req.body;
// Check cache
const cacheKey = CacheManager.generateKey({ prompt, context, mode });
const cached = await CacheManager.get(cacheKey);
if (cached && !options.noCache) {
MetricsCollector.recordCacheHit(requestId);
return res.json({
...cached,
cached: true,
requestId
});
}
// Prepare system prompt based on mode
const systemPrompt = getSystemPrompt(mode);
// Execute Claude request
const completion = await anthropic.messages.create({
model: options.model || 'claude-3-sonnet-20240229',
max_tokens: options.maxTokens || 4000,
temperature: options.temperature || 0.7,
system: systemPrompt,
messages: [{
role: 'user',
content: formatPrompt(prompt, context)
}]
});
const response = {
success: true,
output: completion.content[0].text,
mode,
usage: {
promptTokens: completion.usage.input_tokens,
completionTokens: completion.usage.output_tokens,
totalTokens: completion.usage.input_tokens + completion.usage.output_tokens
},
requestId,
timestamp: new Date().toISOString()
};
// Cache successful responses
await CacheManager.set(cacheKey, response, options.cacheTTL || 3600);
// Record metrics
MetricsCollector.recordRequest({
requestId,
mode,
duration: Date.now() - startTime,
tokens: response.usage.totalTokens,
success: true
});
res.json(response);
} catch (error) {
MetricsCollector.recordError({
requestId,
error: error.message,
mode: req.body.mode
});
res.status(500).json({
success: false,
error: error.message,
requestId,
timestamp: new Date().toISOString()
});
}
});
// System prompts for different modes
function getSystemPrompt(mode: string): string {
const prompts = {
'generate': 'You are Claude Code, an expert software engineer. Generate clean, efficient, and well-documented code.',
'review': 'You are Claude Code, a senior code reviewer. Analyze code for bugs, security issues, and improvements.',
'fix': 'You are Claude Code, a debugging expert. Identify and fix issues in the code.',
'test': 'You are Claude Code, a testing specialist. Generate comprehensive test cases.',
'document': 'You are Claude Code, a technical writer. Create clear and helpful documentation.',
'refactor': 'You are Claude Code, a refactoring expert. Improve code structure while maintaining functionality.'
};
return prompts[mode] || prompts['generate'];
}
// Graceful shutdown
process.on('SIGTERM', async () => {
console.log('SIGTERM received, shutting down gracefully');
await MetricsCollector.flush();
await CacheManager.close();
process.exit(0);
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Claude API server running on port ${PORT}`);
});π Real-World Use Cases
1. Automated PR Review Pipeline
Scenario: Automatically review every pull request with specific focus areas based on the project.
{
"name": "Smart PR Review Pipeline",
"nodes": [
{
"name": "GitHub PR Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": {
"httpMethod": "POST",
"path": "github-pr",
"responseMode": "responseNode"
}
},
{
"name": "Fetch PR Files",
"type": "n8n-nodes-base.github",
"parameters": {
"resource": "file",
"operation": "list",
"owner": "={{ $json.repository.owner.login }}",
"repository": "={{ $json.repository.name }}",
"pullRequestNumber": "={{ $json.pull_request.number }}"
}
},
{
"name": "Categorize Changes",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": `
const files = $input.all();
const categories = {
frontend: [],
backend: [],
database: [],
config: [],
tests: [],
docs: []
};
files.forEach(file => {
const path = file.json.filename;
if (path.includes('frontend/') || path.match(/\\.(jsx?|tsx?|vue|css|scss)$/)) {
categories.frontend.push(file);
} else if (path.includes('backend/') || path.includes('api/')) {
categories.backend.push(file);
} else if (path.includes('migrations/') || path.match(/\\.sql$/)) {
categories.database.push(file);
} else if (path.match(/\\.(json|yml|yaml|env)$/)) {
categories.config.push(file);
} else if (path.includes('test') || path.match(/\\.spec\\.|test\\./)) {
categories.tests.push(file);
} else if (path.match(/\\.(md|rst|txt)$/)) {
categories.docs.push(file);
}
});
return Object.entries(categories)
.filter(([_, files]) => files.length > 0)
.map(([category, files]) => ({
json: { category, files, count: files.length }
}));
`
}
},
{
"name": "Review Each Category",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"method": "POST",
"url": "http://claude-api:3000/execute",
"sendBody": true,
"bodyParameters": {
"prompt": "Review these {{$json.category}} changes:\n\n{{$json.files.map(f => f.json.patch).join('\\n\\n')}}",
"mode": "review",
"context": {
"category": "={{$json.category}}",
"projectType": "={{$json.repository.language}}",
"guidelines": "Focus on: security, performance, best practices, potential bugs"
}
}
}
},
{
"name": "Aggregate Reviews",
"type": "n8n-nodes-base.aggregate",
"parameters": {
"fieldsToAggregate": [
{
"fieldToAggregate": "output",
"renameField": true,
"outputFieldName": "reviews"
}
]
}
},
{
"name": "Format Final Review",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"method": "POST",
"url": "http://claude-api:3000/execute",
"sendBody": true,
"bodyParameters": {
"prompt": "Consolidate these category-specific reviews into a comprehensive PR review",
"mode": "document",
"context": "={{$json}}",
"options": {
"temperature": 0.5
}
}
}
},
{
"name": "Post Review",
"type": "n8n-nodes-base.github",
"parameters": {
"resource": "review",
"operation": "create",
"event": "COMMENT",
"body": "={{$json.output}}"
}
}
]
}2. Intelligent Bug Triage System
Scenario: Automatically triage incoming bugs, assign severity, and suggest fixes.
// Bug Triage Workflow with Claude
const bugTriageWorkflow = {
name: "Intelligent Bug Triage",
nodes: [
{
name: "Bug Report Trigger",
type: "webhook",
parameters: {
path: "bug-report"
}
},
{
name: "Enrich Bug Data",
type: "code",
jsCode: `
const bug = $input.first().json;
// Fetch related data
const relatedIssues = await searchSimilarIssues(bug.title);
const affectedCode = await identifyAffectedCode(bug.description);
const userImpact = calculateUserImpact(bug);
return {
json: {
...bug,
enriched: {
relatedIssues,
affectedCode,
userImpact,
reporterHistory: await getReporterHistory(bug.reporter)
}
}
};
`
},
{
name: "Analyze with Claude",
type: "httpRequest",
parameters: {
url: "http://claude-api:3000/execute",
method: "POST",
body: {
prompt: `Analyze this bug report and provide:
1. Severity assessment (P0-P4)
2. Root cause analysis
3. Suggested fix approach
4. Estimated effort
5. Similar resolved issues
Bug: {{$json.title}}
Description: {{$json.description}}
Stack Trace: {{$json.stackTrace}}
Related Issues: {{$json.enriched.relatedIssues}}
Affected Code: {{$json.enriched.affectedCode}}`,
mode: "analyze",
options: {
model: "claude-3-opus-20240229",
temperature: 0.3
}
}
}
},
{
name: "Create Action Plan",
type: "code",
jsCode: `
const analysis = $input.first().json;
const bug = $node["Bug Report Trigger"].json;
const actionPlan = {
severity: analysis.severity,
assignee: selectAssignee(analysis.affectedCode, analysis.estimatedEffort),
labels: generateLabels(analysis),
milestone: selectMilestone(analysis.severity),
actions: []
};
// P0/P1: Immediate action
if (['P0', 'P1'].includes(analysis.severity)) {
actionPlan.actions.push({
type: 'page_oncall',
team: determineTeam(analysis.affectedCode)
});
actionPlan.actions.push({
type: 'create_incident',
severity: analysis.severity
});
}
// Generate fix if confidence is high
if (analysis.confidence > 0.8 && analysis.suggestedFix) {
actionPlan.actions.push({
type: 'generate_fix',
approach: analysis.suggestedFix
});
}
return { json: actionPlan };
`
},
{
name: "Execute Actions",
type: "split",
parameters: {
// Split into parallel execution paths
}
}
]
};3. Self-Healing Infrastructure
Scenario: Detect and automatically fix common infrastructure issues.
// Self-Healing Infrastructure Workflow
const selfHealingWorkflow = {
name: "Self-Healing Infrastructure",
trigger: {
type: "prometheus-alert",
conditions: ["high_error_rate", "service_down", "performance_degradation"]
},
healingStrategies: {
high_error_rate: async (alert) => {
// Analyze logs to identify error patterns
const logs = await fetchRecentLogs(alert.service, '5m');
const analysis = await claude({
prompt: `Analyze these error logs and suggest remediation:
Service: ${alert.service}
Error Rate: ${alert.errorRate}
Logs: ${logs}
Provide:
1. Root cause
2. Immediate fix
3. Long-term solution`,
mode: 'fix'
});
// Execute immediate fixes
if (analysis.immediateFix.type === 'restart') {
await restartService(alert.service);
} else if (analysis.immediateFix.type === 'scale') {
await scaleService(alert.service, analysis.immediateFix.replicas);
} else if (analysis.immediateFix.type === 'rollback') {
await rollbackDeployment(alert.service);
}
// Create ticket for long-term fix
await createTicket({
title: `Self-healing: ${alert.service} - ${analysis.rootCause}`,
description: analysis.longTermSolution,
priority: 'P2'
});
},
service_down: async (alert) => {
const diagnostics = await runDiagnostics(alert.service);
const recovery = await claude({
prompt: `Service ${alert.service} is down. Diagnostics: ${JSON.stringify(diagnostics)}
Suggest recovery steps prioritized by likelihood of success.`,
mode: 'fix'
});
for (const step of recovery.steps) {
const result = await executeRecoveryStep(step);
if (result.success) {
await notifySuccess(alert, step);
break;
}
}
}
}
};4. Continuous Documentation Pipeline
Scenario: Keep documentation automatically updated as code changes.
Documentation Pipeline:
Triggers:
- Git push to main
- PR merged
- API schema change
- New feature flag
Process:
1. Detect what changed
2. Identify affected documentation
3. Generate updates with Claude
4. Create PR with changes
5. Request human review for significant changes// Documentation Update Workflow
const docUpdateWorkflow = {
name: "Continuous Documentation",
detectChanges: async (commit) => {
const changes = {
api: [],
components: [],
config: [],
features: []
};
// Analyze commit
const files = await getCommitFiles(commit);
for (const file of files) {
if (file.path.includes('/api/')) {
changes.api.push(await analyzeAPIChange(file));
} else if (file.path.includes('/components/')) {
changes.components.push(await analyzeComponentChange(file));
}
// ... more categories
}
return changes;
},
updateDocumentation: async (changes) => {
const updates = [];
// API documentation
if (changes.api.length > 0) {
const apiDocs = await claude({
prompt: `Update API documentation based on these changes:
${JSON.stringify(changes.api)}
Include:
- Endpoint changes
- Parameter updates
- Response format changes
- Breaking changes callout
- Migration guide if needed`,
mode: 'document'
});
updates.push({
file: 'docs/api/README.md',
content: apiDocs,
reviewRequired: changes.api.some(c => c.breaking)
});
}
// Component documentation
if (changes.components.length > 0) {
for (const component of changes.components) {
const componentDoc = await generateComponentDoc(component);
updates.push({
file: `docs/components/${component.name}.md`,
content: componentDoc,
reviewRequired: component.publicAPI
});
}
}
return updates;
},
generateComponentDoc: async (component) => {
return await claude({
prompt: `Generate documentation for this React component:
${component.code}
Include:
- Purpose and use cases
- Props documentation with types
- Usage examples
- Common patterns
- Accessibility notes`,
mode: 'document',
options: {
temperature: 0.3
}
});
}
};5. Intelligent Release Management
Scenario: Automate release notes, changelog generation, and deployment decisions.
// Intelligent Release Management
interface ReleaseWorkflow {
analyzeChanges(): Promise<ReleaseAnalysis>;
generateReleaseNotes(): Promise<string>;
assessRisk(): Promise<RiskAssessment>;
planDeployment(): Promise<DeploymentPlan>;
}
const releaseManagement: ReleaseWorkflow = {
async analyzeChanges() {
const commits = await getCommitsSinceLastRelease();
const prs = await getMergedPRs();
const issues = await getClosedIssues();
const analysis = await claude({
prompt: `Analyze these changes for release:
Commits: ${commits.length}
PRs: ${prs.map(pr => `#${pr.number}: ${pr.title}`).join('\n')}
Issues: ${issues.map(i => `#${i.number}: ${i.title}`).join('\n')}
Categorize into:
- Features
- Improvements
- Bug fixes
- Breaking changes
- Security updates`,
mode: 'analyze'
});
return analysis;
},
async generateReleaseNotes() {
const analysis = await this.analyzeChanges();
const previousNotes = await getPreviousReleaseNotes(3);
const notes = await claude({
prompt: `Generate release notes following our style:
Version: ${await getNextVersion()}
Changes: ${JSON.stringify(analysis)}
Previous release notes for style reference:
${previousNotes}
Include:
- Highlights section
- Detailed changes by category
- Migration guide for breaking changes
- Credits to contributors`,
mode: 'document'
});
return notes;
},
async assessRisk() {
const changes = await this.analyzeChanges();
const testResults = await getTestResults();
const perfMetrics = await getPerformanceMetrics();
const risk = await claude({
prompt: `Assess deployment risk:
Changes: ${JSON.stringify(changes)}
Test Coverage: ${testResults.coverage}%
Failed Tests: ${testResults.failed}
Performance Impact: ${perfMetrics.summary}
Provide:
- Risk score (1-10)
- Key risks
- Mitigation strategies
- Recommended deployment approach`,
mode: 'analyze'
});
return risk;
},
async planDeployment() {
const risk = await this.assessRisk();
let strategy;
if (risk.score <= 3) {
strategy = 'direct';
} else if (risk.score <= 6) {
strategy = 'canary';
} else {
strategy = 'blue-green';
}
const plan = await claude({
prompt: `Create deployment plan:
Strategy: ${strategy}
Risks: ${JSON.stringify(risk.keyRisks)}
Include:
- Pre-deployment checklist
- Deployment steps
- Validation steps
- Rollback plan
- Communication plan`,
mode: 'generate'
});
return {
strategy,
risk,
plan,
estimatedDuration: calculateDeploymentDuration(strategy)
};
}
};π Performance Optimization
Caching Strategy
// Intelligent Caching for Claude Responses
class ClaudeCacheManager {
private redis: Redis;
private cacheStats = {
hits: 0,
misses: 0,
evictions: 0
};
async get(key: string): Promise<CachedResponse | null> {
const cached = await this.redis.get(key);
if (!cached) {
this.cacheStats.misses++;
return null;
}
const data = JSON.parse(cached);
// Validate cache freshness
if (await this.isStale(data)) {
await this.refresh(key, data);
return await this.get(key);
}
this.cacheStats.hits++;
return data;
}
async set(key: string, value: any, options: CacheOptions = {}) {
const ttl = options.ttl || this.calculateTTL(value);
const cacheData = {
value,
timestamp: Date.now(),
metadata: {
tokens: value.usage?.totalTokens,
mode: value.mode,
hash: this.contentHash(value)
}
};
await this.redis.setex(key, ttl, JSON.stringify(cacheData));
}
private calculateTTL(response: any): number {
// Dynamic TTL based on content type
const baseTTL = {
'generate': 3600, // 1 hour
'review': 7200, // 2 hours
'document': 86400, // 24 hours
'analyze': 1800 // 30 minutes
};
let ttl = baseTTL[response.mode] || 3600;
// Adjust based on token usage (expensive responses cached longer)
if (response.usage?.totalTokens > 2000) {
ttl *= 2;
}
return ttl;
}
private async isStale(data: CachedResponse): boolean {
// Check if underlying code has changed
if (data.metadata.codeVersion) {
const currentVersion = await this.getCodeVersion();
if (currentVersion !== data.metadata.codeVersion) {
return true;
}
}
// Check if dependencies have updated
if (data.metadata.dependencies) {
const currentDeps = await this.getDependencyVersions();
if (!this.depsMatch(currentDeps, data.metadata.dependencies)) {
return true;
}
}
return false;
}
}Batch Processing
// Batch Processing for Multiple Claude Requests
class ClaudeBatchProcessor {
private queue: BatchRequest[] = [];
private processing = false;
async add(request: BatchRequest): Promise<BatchResponse> {
const promise = new Promise<BatchResponse>((resolve, reject) => {
this.queue.push({
...request,
resolve,
reject
});
});
if (!this.processing) {
this.processBatch();
}
return promise;
}
private async processBatch() {
this.processing = true;
while (this.queue.length > 0) {
// Take up to 10 requests
const batch = this.queue.splice(0, 10);
if (batch.length === 1) {
// Single request, process normally
await this.processSingle(batch[0]);
} else {
// Multiple requests, optimize
await this.processMultiple(batch);
}
// Rate limiting pause
await this.rateLimitPause();
}
this.processing = false;
}
private async processMultiple(batch: BatchRequest[]) {
// Group similar requests
const grouped = this.groupBySimilarity(batch);
for (const group of grouped) {
if (group.length === 1) {
await this.processSingle(group[0]);
continue;
}
// Create combined prompt
const combinedPrompt = this.createCombinedPrompt(group);
try {
const response = await claude({
prompt: combinedPrompt,
mode: 'batch',
options: {
maxTokens: 8000
}
});
// Parse and distribute responses
const parsed = this.parseMultiResponse(response, group);
group.forEach((req, index) => {
req.resolve(parsed[index]);
});
} catch (error) {
group.forEach(req => req.reject(error));
}
}
}
private groupBySimilarity(batch: BatchRequest[]): BatchRequest[][] {
const groups: Map<string, BatchRequest[]> = new Map();
batch.forEach(request => {
const key = `${request.mode}-${request.context?.type || 'default'}`;
if (!groups.has(key)) {
groups.set(key, []);
}
groups.get(key)!.push(request);
});
return Array.from(groups.values());
}
}π Security Best Practices
Input Validation
// Security Middleware for n8n Claude Integration
class SecurityMiddleware {
static async validateRequest(req: Request, res: Response, next: NextFunction) {
// Validate API key
const apiKey = req.headers['x-api-key'];
if (!apiKey || !await validateApiKey(apiKey)) {
return res.status(401).json({ error: 'Invalid API key' });
}
// Validate request size
if (req.headers['content-length'] > 1024 * 1024) { // 1MB limit
return res.status(413).json({ error: 'Request too large' });
}
// Validate prompt content
const { prompt } = req.body;
if (!prompt || typeof prompt !== 'string') {
return res.status(400).json({ error: 'Invalid prompt' });
}
// Check for injection attempts
if (detectInjection(prompt)) {
await logSecurityEvent('injection_attempt', req);
return res.status(400).json({ error: 'Invalid characters in prompt' });
}
next();
}
static sanitizeInput(req: Request, res: Response, next: NextFunction) {
// Sanitize all string inputs
const sanitize = (obj: any): any => {
if (typeof obj === 'string') {
return obj
.replace(/[<>]/g, '') // Remove potential HTML
.substring(0, 50000); // Limit length
} else if (Array.isArray(obj)) {
return obj.map(sanitize);
} else if (obj && typeof obj === 'object') {
const sanitized: any = {};
for (const [key, value] of Object.entries(obj)) {
sanitized[key] = sanitize(value);
}
return sanitized;
}
return obj;
};
req.body = sanitize(req.body);
next();
}
}Audit Logging
// Comprehensive Audit Logging
class AuditLogger {
async logRequest(req: Request, response: any) {
const auditEntry = {
timestamp: new Date().toISOString(),
requestId: req.headers['x-request-id'],
userId: req.user?.id,
action: 'claude_request',
details: {
mode: req.body.mode,
promptLength: req.body.prompt?.length,
contextType: req.body.context?.type,
tokensUsed: response.usage?.totalTokens,
success: response.success,
duration: response.duration
},
ip: req.ip,
userAgent: req.headers['user-agent']
};
// Store in time-series database
await this.timeSeriesDB.write(auditEntry);
// Alert on suspicious patterns
if (await this.detectAnomalies(auditEntry)) {
await this.alertSecurity(auditEntry);
}
}
private async detectAnomalies(entry: AuditEntry): boolean {
// Check for unusual patterns
const recentRequests = await this.getRecentRequests(entry.userId, '5m');
// Rapid request rate
if (recentRequests.length > 50) {
return true;
}
// Unusual token usage
const avgTokens = this.calculateAverage(recentRequests.map(r => r.details.tokensUsed));
if (entry.details.tokensUsed > avgTokens * 3) {
return true;
}
// Unusual access patterns
const unusualTime = this.isUnusualAccessTime(entry.timestamp, entry.userId);
if (unusualTime) {
return true;
}
return false;
}
}π Monitoring and Observability
Metrics Collection
// Comprehensive Metrics for n8n Claude Integration
class MetricsCollector {
private prometheus = new PrometheusClient();
constructor() {
// Define metrics
this.prometheus.register.registerMetric(
new prometheus.Counter({
name: 'claude_requests_total',
help: 'Total number of Claude requests',
labelNames: ['mode', 'status']
})
);
this.prometheus.register.registerMetric(
new prometheus.Histogram({
name: 'claude_request_duration_seconds',
help: 'Claude request duration in seconds',
labelNames: ['mode'],
buckets: [0.1, 0.5, 1, 2, 5, 10, 30]
})
);
this.prometheus.register.registerMetric(
new prometheus.Gauge({
name: 'claude_tokens_used',
help: 'Tokens used in Claude requests',
labelNames: ['mode', 'type']
})
);
this.prometheus.register.registerMetric(
new prometheus.Gauge({
name: 'n8n_workflow_success_rate',
help: 'Success rate of n8n workflows using Claude',
labelNames: ['workflow_name']
})
);
}
recordRequest(details: RequestDetails) {
// Increment counter
this.prometheus.counter('claude_requests_total')
.labels(details.mode, details.success ? 'success' : 'failure')
.inc();
// Record duration
this.prometheus.histogram('claude_request_duration_seconds')
.labels(details.mode)
.observe(details.duration / 1000);
// Record token usage
if (details.tokens) {
this.prometheus.gauge('claude_tokens_used')
.labels(details.mode, 'prompt')
.set(details.tokens.prompt);
this.prometheus.gauge('claude_tokens_used')
.labels(details.mode, 'completion')
.set(details.tokens.completion);
}
}
async getMetrics(): Promise<string> {
return this.prometheus.register.metrics();
}
}Distributed Tracing
// OpenTelemetry Tracing for n8n Claude Workflows
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('n8n-claude-integration');
class DistributedTracing {
async traceWorkflow(workflowId: string, execution: WorkflowExecution) {
const span = tracer.startSpan('workflow.execute', {
attributes: {
'workflow.id': workflowId,
'workflow.name': execution.workflowName,
'trigger.type': execution.triggerType
}
});
try {
// Trace each node execution
for (const node of execution.nodes) {
await this.traceNode(node, span);
}
span.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
span.recordException(error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message
});
throw error;
} finally {
span.end();
}
}
private async traceNode(node: NodeExecution, parentSpan: Span) {
const nodeSpan = tracer.startSpan(`node.${node.type}`, {
parent: parentSpan,
attributes: {
'node.id': node.id,
'node.name': node.name,
'node.type': node.type
}
});
context.with(trace.setSpan(context.active(), nodeSpan), async () => {
try {
if (node.type === 'claude') {
await this.traceClaudeRequest(node, nodeSpan);
} else {
await node.execute();
}
nodeSpan.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
nodeSpan.recordException(error);
nodeSpan.setStatus({
code: SpanStatusCode.ERROR,
message: error.message
});
throw error;
} finally {
nodeSpan.end();
}
});
}
private async traceClaudeRequest(node: NodeExecution, parentSpan: Span) {
const claudeSpan = tracer.startSpan('claude.request', {
parent: parentSpan,
attributes: {
'claude.mode': node.parameters.mode,
'claude.model': node.parameters.model || 'default'
}
});
try {
const start = Date.now();
const response = await executeClaudeRequest(node.parameters);
claudeSpan.setAttributes({
'claude.tokens.prompt': response.usage.promptTokens,
'claude.tokens.completion': response.usage.completionTokens,
'claude.duration': Date.now() - start
});
return response;
} finally {
claudeSpan.end();
}
}
}π Deployment Strategies
Blue-Green Deployment
# Blue-Green Deployment for n8n Claude Integration
version: '3.8'
services:
# Blue Environment (Current Production)
n8n-blue:
image: n8nio/n8n:${BLUE_VERSION}
networks:
- blue-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n-blue.rule=Host(`n8n.production.com`) && Headers(`X-Version`, `blue`)"
claude-api-blue:
image: claude-api:${BLUE_VERSION}
networks:
- blue-network
# Green Environment (New Version)
n8n-green:
image: n8nio/n8n:${GREEN_VERSION}
networks:
- green-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n-green.rule=Host(`n8n.production.com`) && Headers(`X-Version`, `green`)"
claude-api-green:
image: claude-api:${GREEN_VERSION}
networks:
- green-network
# Load Balancer
traefik:
image: traefik:v2.10
command:
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- blue-network
- green-network
networks:
blue-network:
green-network:Canary Deployment
// Canary Deployment Controller
class CanaryDeployment {
private trafficSplitPercentage = 5; // Start with 5% traffic
async deploy(newVersion: string) {
// Deploy canary instance
await this.deployCanaryInstance(newVersion);
// Monitor metrics
const monitoring = setInterval(async () => {
const metrics = await this.collectCanaryMetrics();
if (this.isHealthy(metrics)) {
// Gradually increase traffic
this.trafficSplitPercentage = Math.min(100, this.trafficSplitPercentage + 10);
await this.updateTrafficSplit(this.trafficSplitPercentage);
if (this.trafficSplitPercentage === 100) {
// Full deployment successful
clearInterval(monitoring);
await this.promoteCanary();
}
} else {
// Rollback
clearInterval(monitoring);
await this.rollback();
}
}, 60000); // Check every minute
}
private isHealthy(metrics: CanaryMetrics): boolean {
return (
metrics.errorRate < 0.01 && // Less than 1% errors
metrics.responseTime < 2000 && // Less than 2s response time
metrics.successRate > 0.99 // Greater than 99% success
);
}
}π Lessons Learned
Common Pitfalls and Solutions
-
Token Limit Exceeded
- Solution: Implement context windowing
- Use summarization for long inputs
- Split large operations into chunks
-
Rate Limiting Issues
- Solution: Implement exponential backoff
- Use request queuing
- Cache responses aggressively
-
Inconsistent Responses
- Solution: Use lower temperature for consistency
- Implement response validation
- Add retry logic with rephrasing
-
Cost Management
- Solution: Implement usage quotas
- Use cheaper models for simple tasks
- Aggressive caching strategy
-
Debugging Complex Workflows
- Solution: Comprehensive logging
- Distributed tracing
- Workflow visualization tools
π Resources
Based on real production deployments and community feedback Last Updated: 2025-07-21