Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 32 additions & 25 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### 🚀 Major Features
- **🌳 Git Worktree Support**: Branch-based task isolation with automatic creation/cleanup
- Isolated task execution in separate worktrees
- Multiple merge strategies: PR, auto, manual, patch
- Automatic branch creation and management
- Configurable cleanup behavior
- **🔀 GitHub Integration**: Automatic PR creation and management
- Create PRs with custom titles and descriptions
- Automatic PR status checks
- Push branches to remote automatically
- Secure command execution with simple-git
- **🔄 Retry Logic**: Exponential backoff for transient failures
- Smart error detection (network, rate limits, timeouts)
- Configurable retry attempts and delays
- Applied to git operations and GitHub API calls
*No unreleased changes at this time.*

---

## [0.3.1] - Unreleased

### 🔒 Security Fixes
- **CRITICAL: Graph Corruption Fix (Issue #28)**: Deep copy in `wouldCreateCycle()` prevents dependency graph corruption
- Shallow copy (`new Map(this.graph)`) corrupted dependency graph because Set values remained as references
- Cycle detection could permanently add edges to the graph, causing unpredictable task execution
- Fixed with proper deep copy: `new Map(Array.from(this.graph.entries()).map(([k, v]) => [k, new Set(v)]))`
- **npm audit vulnerabilities fixed**: Resolved 3 security issues (1 HIGH, 2 MODERATE)
- glob CLI command injection (HIGH) - GHSA-5j98-mcp5-4vw2
- body-parser denial of service (MODERATE) - GHSA-wqch-xfxh-vrr4
- vite path traversal on Windows (MODERATE) - GHSA-93m4-6634-74q7
- **Configuration validation logging**: No longer silently falls back to defaults when env vars fail validation

### 🚀 Performance Improvements
- **Settling workers tracking**: Prevents spawn burst overload during high-load scenarios
- Load average is a 1-minute rolling average that doesn't reflect recent spawns
- New `recordSpawn()` tracks workers in 15-second settling window
- Projects resource usage including workers not yet reflected in metrics
- Increased `minSpawnDelayMs` from 50ms to 1000ms for additional protection

### 🛠️ Technical Improvements
- **Security Enhancements**: Replaced shell command execution with secure libraries
- Migrated from exec/shell to simple-git for git operations
- Command injection prevention through proper argument handling
- Array-based command execution instead of string concatenation
- **CLI/MCP Alignment**: Perfect parameter parity between interfaces
- Added `--tail` parameter to CLI logs command
- Added optional reason parameter to CLI cancel command
- All MCP parameters now available in CLI
- **Type safety**: Replaced `any` type with `Worker` in `getWorkerStats()` return type
- **Test compatibility**: Added `recordSpawn()` to TestResourceMonitor
- **🔒 Input Validation Limits (Issue #12)**: Security hardening for dependency system
- Maximum 100 dependencies per task to prevent DoS attacks
- Maximum 100 dependency chain depth to prevent stack overflow
Expand All @@ -52,12 +54,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- **Parameter Consistency**: Aligned CLI and MCP tool parameters

### 🧪 Test Coverage
- **18 new tests** for v0.3.1 security and consistency improvements:
- **5 new tests** for settling workers tracking in ResourceMonitor
- **3 regression tests** for graph immutability after cycle checks
- **18 tests** for v0.3.1 security and consistency improvements:
- 11 tests for atomic batch dependency operations (rollback, validation)
- 3 tests for max dependencies per task validation (100 limit)
- 1 test for max chain depth validation (100 limit)
- 7 tests for DependencyGraph.getMaxDepth() algorithm
- **All 221 unit tests passing** (was 203 tests before v0.3.1)

### 📚 Documentation
- Updated `docs/architecture/TASK_ARCHITECTURE.md` with correct deep copy pattern
- Fixed stale line numbers in `docs/TASK-DEPENDENCIES.md` (wouldCreateCycle at line 240)

## [0.3.0] - 2025-10-18

Expand Down
6 changes: 6 additions & 0 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ Last Updated: September 2025
- **Worker Lifecycle**: Automatic cleanup on completion/failure
- **Resource Tracking**: Per-worker CPU and memory monitoring

### Settling Workers Tracking (v0.3.1+)
- **Problem Solved**: Load average is a 1-minute rolling average that doesn't reflect recent spawns
- **Settling Window**: Recently spawned workers are tracked for 15 seconds (configurable via `WORKER_SETTLING_WINDOW_MS`)
- **Resource Projection**: Includes settling workers in resource calculations to prevent spawn burst overload
- **Spawn Delay**: Minimum 1 second between spawns for stability (configurable via `WORKER_MIN_SPAWN_DELAY_MS`)

## ✅ Task Persistence & Recovery

### Database Storage
Expand Down
4 changes: 2 additions & 2 deletions docs/TASK-DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ If dependency operations are slow:

### Code References

- Cycle detection: `src/core/dependency-graph.ts:50` (wouldCreateCycle method)
- Cycle detection: `src/core/dependency-graph.ts:240` (wouldCreateCycle method)
- Dependency-aware queueing: `src/services/handlers/queue-handler.ts:63` (handleTaskPersisted)
- Dependency resolution: `src/services/handlers/dependency-handler.ts:199` (resolveDependencies)
- Dependency resolution: `src/services/handlers/dependency-handler.ts:344` (resolveDependencies)
- Task unblocking: `src/services/handlers/queue-handler.ts:306` (handleTaskUnblocked)
11 changes: 7 additions & 4 deletions docs/architecture/TASK_ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,15 +497,18 @@ export class DependencyGraph {
```

### 7.2 Cycle Detection Algorithm
**File**: `/workspace/claudine/src/core/dependency-graph.ts` (Lines 73-110)
**File**: `/workspace/claudine/src/core/dependency-graph.ts` (Lines 240-280)

```typescript
wouldCreateCycle(taskId: TaskId, dependsOnTaskId: TaskId): Result<boolean> {
// 1. Check self-dependency
if (taskId === dependsOnTaskId) return ok(true);

// 2. Create temporary graph with proposed edge
const tempGraph = new Map(this.graph);

// 2. Create temporary graph with proposed edge (DEEP COPY required!)
// Shallow copy (new Map(this.graph)) corrupts graph - Set values are references
const tempGraph = new Map(
Array.from(this.graph.entries()).map(([k, v]) => [k, new Set(v)])
);
tempGraph.get(taskId)!.add(dependsOnTaskId);

// 3. Run DFS to detect cycle
Expand Down
96 changes: 49 additions & 47 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions src/core/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export const ConfigurationSchema = z.object({
// Process management configuration
killGracePeriodMs: z.number().min(1000).max(60000).default(5000), // Default: 5 second grace period
resourceMonitorIntervalMs: z.number().min(1000).max(60000).default(5000), // Default: check every 5 seconds
minSpawnDelayMs: z.number().min(10).max(10000).default(50), // Default: 50ms burst protection (reduced from 100ms for better responsiveness)
minSpawnDelayMs: z.number().min(10).max(30000).default(1000), // Default: 1s minimum delay between spawns (with settling worker tracking)
settlingWindowMs: z.number().min(5000).max(60000).default(15000), // Default: 15s settling window for newly spawned workers
// Event system configuration
eventRequestTimeoutMs: z.number().min(1000).max(300000).default(5000), // Default: 5 second timeout
eventCleanupIntervalMs: z.number().min(10000).max(600000).default(60000), // Default: cleanup every minute
Expand Down Expand Up @@ -66,7 +67,8 @@ const DEFAULT_CONFIG: Configuration = {
// Process management defaults
killGracePeriodMs: 5000, // Default: 5 seconds grace period for process termination
resourceMonitorIntervalMs: 5000, // Default: check resources every 5 seconds
minSpawnDelayMs: 50, // Default: 50ms burst protection between worker spawns
minSpawnDelayMs: 1000, // Default: 1s minimum delay between spawns (with settling worker tracking)
settlingWindowMs: 15000, // Default: 15s settling window for newly spawned workers
// Event system defaults
eventRequestTimeoutMs: 5000, // Default: 5 second timeout for event requests
eventCleanupIntervalMs: 60000, // Default: cleanup event history every minute
Expand Down Expand Up @@ -118,6 +120,7 @@ export function loadConfiguration(): Configuration {
if (process.env.PROCESS_KILL_GRACE_PERIOD_MS) envConfig.killGracePeriodMs = parseEnvNumber(process.env.PROCESS_KILL_GRACE_PERIOD_MS, 0);
if (process.env.RESOURCE_MONITOR_INTERVAL_MS) envConfig.resourceMonitorIntervalMs = parseEnvNumber(process.env.RESOURCE_MONITOR_INTERVAL_MS, 0);
if (process.env.WORKER_MIN_SPAWN_DELAY_MS) envConfig.minSpawnDelayMs = parseEnvNumber(process.env.WORKER_MIN_SPAWN_DELAY_MS, 0);
if (process.env.WORKER_SETTLING_WINDOW_MS) envConfig.settlingWindowMs = parseEnvNumber(process.env.WORKER_SETTLING_WINDOW_MS, 0);
if (process.env.EVENT_REQUEST_TIMEOUT_MS) envConfig.eventRequestTimeoutMs = parseEnvNumber(process.env.EVENT_REQUEST_TIMEOUT_MS, 0);
if (process.env.EVENT_CLEANUP_INTERVAL_MS) envConfig.eventCleanupIntervalMs = parseEnvNumber(process.env.EVENT_CLEANUP_INTERVAL_MS, 0);
if (process.env.FILE_STORAGE_THRESHOLD_BYTES) envConfig.fileStorageThresholdBytes = parseEnvNumber(process.env.FILE_STORAGE_THRESHOLD_BYTES, 0);
Expand All @@ -131,6 +134,14 @@ export function loadConfiguration(): Configuration {
if (parseResult.success) {
return parseResult.data; // Guaranteed complete and valid
} else {
// SECURITY: Log warning when config validation fails (don't silently fallback)
// This helps users discover misconfigured environment variables
const errors = parseResult.error.errors.map(e =>
` - ${e.path.join('.')}: ${e.message}`
).join('\n');
console.warn(
`[Claudine] Configuration validation failed, using defaults:\n${errors}`
);
// If validation fails (invalid env values), use pure defaults from schema
return ConfigurationSchema.parse({}); // Empty object gets all defaults
}
Expand Down
7 changes: 6 additions & 1 deletion src/core/dependency-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,12 @@ export class DependencyGraph {
}

// Create temporary graph with the proposed edge
const tempGraph = new Map(this.graph);
// SECURITY FIX (Issue #28): Deep copy required to prevent graph corruption
// Shallow copy (new Map(this.graph)) only copies Map structure - Set values are REFERENCES
// When we modify temp graph's Sets, we would mutate the original graph's Sets
const tempGraph = new Map(
Array.from(this.graph.entries()).map(([k, v]) => [k, new Set(v)])
);

// Add proposed edge to temp graph
if (!tempGraph.has(taskIdStr)) {
Expand Down
6 changes: 6 additions & 0 deletions src/core/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export interface ResourceMonitor {
};
incrementWorkerCount(): void;
decrementWorkerCount(): void;
/**
* Record a spawn event for settling worker tracking
* Call immediately after spawning to track workers during their settling period
* (before they appear in system metrics like load average)
*/
recordSpawn(): void;
}

/**
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING] Architecture: Optional Method Pattern Inconsistency

Making recordSpawn() optional violates the Interface Segregation Principle (ISP). This creates caller burden (requires optional chaining recordSpawn?.()) and implementation ambiguity.

Option 1: Make it required with no-op default (preferred)

export interface ResourceMonitor {
  // ... existing methods ...
  recordSpawn(): void;  // Required, implementors can no-op
}

Option 2: Use Interface Segregation

export interface ResourceMonitor { /* base methods */ }
export interface SettlingAwareResourceMonitor extends ResourceMonitor {
  recordSpawn(): void;
}

Comparison

Approach Pros Cons
Required method Simple, consistent contract Breaks existing implementations
Interface segregation Type-safe, backward compatible More complex type hierarchy

Recommended: Option 1 - Simpler and forces implementors to consciously handle spawn tracking.


From: architecture audit | Severity: MEDIUM


Generated by Claude Code via /code-review

Expand Down
Loading
Loading