(lockFilePath: string)
| 162 | * Check if a lock file represents an active lock (process still running) |
| 163 | */ |
| 164 | export function isLockActive(lockFilePath: string): boolean { |
| 165 | const content = readLockContent(lockFilePath) |
| 166 | |
| 167 | if (!content) { |
| 168 | return false |
| 169 | } |
| 170 | |
| 171 | const { pid, execPath } = content |
| 172 | |
| 173 | // Primary check: is the process running? |
| 174 | if (!isProcessRunning(pid)) { |
| 175 | return false |
| 176 | } |
| 177 | |
| 178 | // Secondary validation: is it actually a Claude process? |
| 179 | // This helps with PID reuse scenarios |
| 180 | if (!isClaudeProcess(pid, execPath)) { |
| 181 | logForDebugging( |
| 182 | `Lock PID ${pid} is running but does not appear to be Claude - treating as stale`, |
| 183 | ) |
| 184 | return false |
| 185 | } |
| 186 | |
| 187 | // Fallback: if the lock is very old (> 2 hours) and we can't validate |
| 188 | // the command, be conservative and consider it potentially stale |
| 189 | // This handles edge cases like network filesystems |
| 190 | const fs = getFsImplementation() |
| 191 | try { |
| 192 | const stats = fs.statSync(lockFilePath) |
| 193 | const age = Date.now() - stats.mtimeMs |
| 194 | if (age > FALLBACK_STALE_MS) { |
| 195 | // Double-check that we can still see the process |
| 196 | if (!isProcessRunning(pid)) { |
| 197 | return false |
| 198 | } |
| 199 | } |
| 200 | } catch { |
| 201 | // If we can't stat the file, trust the PID check |
| 202 | } |
| 203 | |
| 204 | return true |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * Write lock content to a file atomically |
no test coverage detected