[FEATURE] Add session lock file

Resolved 💬 4 comments Opened Jan 19, 2026 by liamhelmer Closed Feb 28, 2026

Preflight Checklist

  • [x] I have searched existing feature requests and this hasn't been requested before
  • [x] This is a single feature request (not multiple features)

---

Problem Statement

When building orchestration systems that manage multiple Claude Code sessions, there's no reliable way to detect which sessions are currently active or to coordinate between processes. External tools need to poll Claude Code's state or resort to process inspection, which is fragile and platform-dependent.

An orchestrator managing parallel Claude Code sessions needs to:

  1. Know which sessions are currently active
  2. Avoid conflicting with active sessions on the same project
  3. Clean up stale sessions after crashes

---

Proposed Solution

Claude Code should create a lock file at ~/.claude/projects/{project}/{session}.lock (or {session}.pid) when a session starts and remove it when the session ends gracefully.

Lock file contents:

{
  "pid": 12345,
  "startedAt": "2025-01-19T10:30:00Z",
  "sessionId": "session-abc123",
  "cwd": "/Users/user/myproject"
}

Orchestrator integration example:

import { readdir, readFile, stat } from 'fs/promises';
import { join } from 'path';
import { homedir } from 'os';

interface SessionLock {
  pid: number;
  startedAt: string;
  sessionId: string;
  cwd: string;
}

async function getActiveClaudeSessions(projectHash: string): Promise<SessionLock[]> {
  const lockDir = join(homedir(), '.claude', 'projects', projectHash);
  const files = await readdir(lockDir);
  const lockFiles = files.filter(f => f.endsWith('.lock'));
  
  const sessions: SessionLock[] = [];
  for (const file of lockFiles) {
    const content = await readFile(join(lockDir, file), 'utf-8');
    const lock: SessionLock = JSON.parse(content);
    
    // Verify process is still running
    if (isProcessAlive(lock.pid)) {
      sessions.push(lock);
    }
  }
  return sessions;
}

function isProcessAlive(pid: number): boolean {
  try {
    process.kill(pid, 0);
    return true;
  } catch {
    return false;
  }
}

// Usage: Wait for exclusive project access
async function waitForProjectAvailable(projectHash: string): Promise<void> {
  while ((await getActiveClaudeSessions(projectHash)).length > 0) {
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
}

Python orchestrator example:

import json
import os
from pathlib import Path
from dataclasses import dataclass
from datetime import datetime

@dataclass
class SessionLock:
    pid: int
    started_at: datetime
    session_id: str
    cwd: str

def get_active_sessions(project_hash: str) -> list[SessionLock]:
    lock_dir = Path.home() / ".claude" / "projects" / project_hash
    sessions = []
    
    for lock_file in lock_dir.glob("*.lock"):
        with open(lock_file) as f:
            data = json.load(f)
        
        # Check if process is still alive
        try:
            os.kill(data["pid"], 0)
            sessions.append(SessionLock(
                pid=data["pid"],
                started_at=datetime.fromisoformat(data["startedAt"]),
                session_id=data["sessionId"],
                cwd=data["cwd"]
            ))
        except ProcessLookupError:
            # Stale lock file - process no longer exists
            lock_file.unlink()  # Clean up
    
    return sessions

---

Priority

Medium - Enables new integration patterns but workarounds exist

---

Feature Category

Developer tools/SDK

---

Alternative Solutions

Current workarounds are fragile:

  1. Process inspection - Parse ps aux | grep claude output (platform-specific, unreliable)
  2. File watching - Monitor ~/.claude for changes (high overhead, race conditions)
  3. Custom wrapper scripts - Maintain external state (duplicates what Claude Code should track)

---

Use Case Example

Scenario: CI system running parallel Claude Code agents

  1. CI job spawns 4 Claude Code instances for different tasks
  2. Orchestrator checks ~/.claude/projects/*/ for {session}.lock files
  3. If a task fails, orchestrator can identify the stuck session by PID
  4. Orchestrator waits for all locks to clear before marking job complete
  5. On crash recovery, orchestrator cleans up stale locks where PID no longer exists

---

Additional Context

This aligns with standard Unix conventions (e.g., /var/run/*.pid files) and would make Claude Code more composable in larger automation systems. The lock file approach is battle-tested in tools like npm, yarn, and many database systems.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗