Major refactoring to hexagonal (ports & adapters) architecture: - Add service layer (apikey_service, project_service) for business logic - Add webhook system with dispatcher and delivery tracking - Add command queue with priority-based processing - Add rate limiting with sliding window algorithm - Add audit logging for command execution - Add OpenTelemetry integration (traces, metrics, spans) - Add circuit breaker for fault tolerance - Add cached repository wrapper for performance - Add comprehensive validation package - Add Kubernetes client integration for pod management - Add database migrations (allowed_ips, audit_log, rate_limiting, queue, webhooks) - Add network policy and PodDisruptionBudget for k8s - Remove legacy executor and projects/registry packages - Untrack secrets.yaml (now managed via envault) - Add coverage.out to .gitignore - Add e2e test infrastructure with docker-compose - Add comprehensive documentation (API, architecture, operations, plans) - Add golangci-lint config and pre-commit hook Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.3 KiB
6.3 KiB
Error Handling Guide
This guide covers error responses from the rdev API and how to handle them.
Error Response Format
All errors follow this format:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description"
},
"meta": {
"request_id": "req-abc123",
"timestamp": "2024-01-15T10:30:00Z"
}
}
Error Codes
Authentication Errors (4xx)
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
UNAUTHORIZED |
401 | Missing or invalid API key | Check API key header |
KEY_REVOKED |
401 | API key has been revoked | Request new key |
KEY_EXPIRED |
401 | API key has expired | Request new key |
FORBIDDEN |
403 | Insufficient permissions | Use key with required scope |
IP_NOT_ALLOWED |
403 | IP not in allowlist | Use allowed IP or update key |
Resource Errors (4xx)
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
BAD_REQUEST |
400 | Invalid request body | Check request format |
NOT_FOUND |
404 | Resource not found | Verify resource ID |
TOO_MANY_REQUESTS |
429 | Rate limit exceeded | Wait and retry |
Server Errors (5xx)
| Code | HTTP Status | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR |
500 | Server error | Retry later, contact support |
SERVICE_UNAVAILABLE |
503 | Service not ready | Wait for service to be ready |
Handling Errors by Type
Authentication Errors
async function handleAuthError(response) {
const { error } = await response.json();
switch (error.code) {
case 'UNAUTHORIZED':
// Key is missing or invalid
throw new Error('Invalid API key. Check your configuration.');
case 'KEY_REVOKED':
// Key was revoked by admin
throw new Error('API key was revoked. Request a new key.');
case 'KEY_EXPIRED':
// Key has expired
throw new Error('API key expired. Request a new key.');
case 'FORBIDDEN':
// Key lacks required scope
throw new Error(`Insufficient permissions: ${error.message}`);
case 'IP_NOT_ALLOWED':
// IP not in allowlist
throw new Error('Your IP is not allowed for this API key.');
default:
throw new Error(error.message);
}
}
Rate Limiting
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('X-RateLimit-Reset');
const waitMs = retryAfter
? (parseInt(retryAfter) * 1000) - Date.now()
: 1000 * Math.pow(2, i); // Exponential backoff
console.log(`Rate limited. Waiting ${waitMs}ms...`);
await new Promise(resolve => setTimeout(resolve, waitMs));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
Validation Errors
async function handleValidationError(response) {
const { error } = await response.json();
// Error message contains field-specific info
// e.g., "prompt: is required"
// e.g., "command: contains dangerous characters"
const match = error.message.match(/^(\w+): (.+)$/);
if (match) {
const [, field, message] = match;
return {
field,
message,
};
}
return { message: error.message };
}
Error Handling Best Practices
1. Always Check Status Code
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
throw new APIError(response.status, error.error);
}
return response.json();
2. Use Custom Error Class
class APIError extends Error {
constructor(status, error) {
super(error.message);
this.name = 'APIError';
this.status = status;
this.code = error.code;
}
isRetryable() {
return this.status >= 500 || this.status === 429;
}
isAuthError() {
return this.status === 401 || this.status === 403;
}
}
3. Log Request IDs
async function logError(response, error) {
const requestId = error.meta?.request_id;
console.error(`Request ${requestId} failed:`, error.error);
// Include in bug reports
return {
requestId,
error: error.error,
url: response.url,
timestamp: new Date().toISOString(),
};
}
4. Implement Circuit Breaker
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failures = 0;
this.threshold = threshold;
this.timeout = timeout;
this.lastFailure = null;
}
async execute(fn) {
if (this.isOpen()) {
throw new Error('Circuit breaker is open');
}
try {
const result = await fn();
this.reset();
return result;
} catch (error) {
this.recordFailure();
throw error;
}
}
isOpen() {
if (this.failures < this.threshold) return false;
if (Date.now() - this.lastFailure > this.timeout) {
this.reset();
return false;
}
return true;
}
recordFailure() {
this.failures++;
this.lastFailure = Date.now();
}
reset() {
this.failures = 0;
this.lastFailure = null;
}
}
Common Error Scenarios
Missing API Key
curl http://localhost:8080/projects
# Response: 401
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing API key"
}
}
Fix: Add the X-API-Key header.
Invalid Command
curl -X POST http://localhost:8080/projects/test/shell \
-H "X-API-Key: rdev_xxx" \
-d '{"command": "rm -rf /"}'
# Response: 400
{
"error": {
"code": "BAD_REQUEST",
"message": "destructive rm command not allowed"
}
}
Fix: Use safe commands. See security documentation for allowed patterns.
Project Not Found
curl http://localhost:8080/projects/nonexistent \
-H "X-API-Key: rdev_xxx"
# Response: 404
{
"error": {
"code": "NOT_FOUND",
"message": "project not found: nonexistent"
}
}
Fix: Check project ID. List projects to see available ones.
Rate Limited
# After too many requests
# Response: 429
{
"error": {
"code": "TOO_MANY_REQUESTS",
"message": "Rate limit exceeded"
}
}
Fix: Wait for X-RateLimit-Reset timestamp, then retry.