mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 16:59:43 +02:00
fix: correct process tree detection and prevent timer starvation
1. Rewrote getActiveChildProcesses() to use a single `ps --ppid` call instead of two-level pgrep. The pane PID is typically claude itself (bash exec'd into it), not a bash wrapper — so direct children of pane_pid ARE the tool processes. 2. Added timer restart in tryStartAiCheck() when skipping due to child processes. Without this, the pre-filter and no-output timers (both one-shot) would never fire again, permanently stalling idle detection for sessions with silent long-running processes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2079,11 +2079,14 @@ export class RespawnController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for active child processes (bash tools, test suites, builds, etc.)
|
// Check for active child processes (bash tools, test suites, builds, etc.)
|
||||||
|
// These may produce no terminal output, so restart timers to retry periodically.
|
||||||
const activeProcesses = this.session.getActiveChildProcesses();
|
const activeProcesses = this.session.getActiveChildProcesses();
|
||||||
if (activeProcesses.length > 0) {
|
if (activeProcesses.length > 0) {
|
||||||
const names = activeProcesses.map((p) => p.command).join(', ');
|
const names = activeProcesses.map((p) => p.command).join(', ');
|
||||||
this.log(`Skipping AI check - ${activeProcesses.length} active child process(es): ${names}`);
|
this.log(`Skipping AI check - ${activeProcesses.length} active child process(es): ${names}`);
|
||||||
this.logAction('detection', `Skipped AI check: child processes running (${names})`);
|
this.logAction('detection', `Skipped AI check: child processes running (${names})`);
|
||||||
|
this.startNoOutputTimer();
|
||||||
|
this.startPreFilterTimer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-48
@@ -542,8 +542,13 @@ export class Session extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the Claude CLI process inside this session's tmux pane has active child processes.
|
* Check if the session's process tree has active child processes beyond Claude itself.
|
||||||
* This detects running bash tools, test suites, builds, servers, etc. that Claude spawned.
|
* Detects running bash tools, test suites, builds, servers, etc. that Claude spawned.
|
||||||
|
*
|
||||||
|
* The tmux pane PID is typically "claude" directly (bash exec'd into it). When Claude
|
||||||
|
* runs a bash tool, it spawns child processes: claude → bash → npm/node/python/etc.
|
||||||
|
* We check direct children of the pane PID, filtering out "claude" itself (for the rare
|
||||||
|
* case where bash wraps claude and didn't exec).
|
||||||
*
|
*
|
||||||
* Returns an array of {pid, command} for each child process, or empty array if none.
|
* Returns an array of {pid, command} for each child process, or empty array if none.
|
||||||
* Returns empty array if no mux session or on error (fail-open to avoid blocking respawn).
|
* Returns empty array if no mux session or on error (fail-open to avoid blocking respawn).
|
||||||
@@ -552,62 +557,29 @@ export class Session extends EventEmitter {
|
|||||||
if (!this._muxSession) return [];
|
if (!this._muxSession) return [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get direct children of the pane PID (typically just the claude process)
|
|
||||||
const panePid = this._muxSession.pid;
|
const panePid = this._muxSession.pid;
|
||||||
const claudeChildren = execSync(`pgrep -P ${panePid}`, {
|
|
||||||
|
// Single call: get direct children with their command names
|
||||||
|
const output = execSync(`ps -o pid=,comm= --ppid ${panePid} 2>/dev/null`, {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
timeout: EXEC_TIMEOUT_MS,
|
timeout: EXEC_TIMEOUT_MS,
|
||||||
}).trim();
|
}).trim();
|
||||||
if (!claudeChildren) return [];
|
if (!output) return [];
|
||||||
|
|
||||||
const claudePids = claudeChildren
|
|
||||||
.split('\n')
|
|
||||||
.map((p) => parseInt(p, 10))
|
|
||||||
.filter((p) => !Number.isNaN(p));
|
|
||||||
|
|
||||||
// For each Claude process, check for its children (the actual running tools/processes)
|
|
||||||
const activeProcesses: { pid: number; command: string }[] = [];
|
const activeProcesses: { pid: number; command: string }[] = [];
|
||||||
for (const claudePid of claudePids) {
|
for (const line of output.split('\n')) {
|
||||||
try {
|
const match = line.trim().match(/^(\d+)\s+(.+)/);
|
||||||
const toolChildren = execSync(`pgrep -P ${claudePid}`, {
|
if (!match) continue;
|
||||||
encoding: 'utf-8',
|
const pid = parseInt(match[1], 10);
|
||||||
timeout: EXEC_TIMEOUT_MS,
|
const command = match[2].trim();
|
||||||
}).trim();
|
// Skip the claude process itself (pane_pid may be bash wrapping claude)
|
||||||
if (!toolChildren) continue;
|
if (command === 'claude') continue;
|
||||||
|
activeProcesses.push({ pid, command });
|
||||||
const toolPids = toolChildren
|
|
||||||
.split('\n')
|
|
||||||
.map((p) => parseInt(p, 10))
|
|
||||||
.filter((p) => !Number.isNaN(p));
|
|
||||||
|
|
||||||
if (toolPids.length === 0) continue;
|
|
||||||
|
|
||||||
// Get command names for the child processes in a single ps call
|
|
||||||
try {
|
|
||||||
const psOutput = execSync(`ps -o pid=,comm= -p ${toolPids.join(',')} 2>/dev/null`, {
|
|
||||||
encoding: 'utf-8',
|
|
||||||
timeout: EXEC_TIMEOUT_MS,
|
|
||||||
}).trim();
|
|
||||||
for (const line of psOutput.split('\n')) {
|
|
||||||
const match = line.trim().match(/^(\d+)\s+(.+)/);
|
|
||||||
if (match) {
|
|
||||||
activeProcesses.push({ pid: parseInt(match[1], 10), command: match[2].trim() });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ps failed — just record PIDs without command names
|
|
||||||
for (const pid of toolPids) {
|
|
||||||
activeProcesses.push({ pid, command: 'unknown' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// No children for this Claude process
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return activeProcesses;
|
return activeProcesses;
|
||||||
} catch {
|
} catch {
|
||||||
// pgrep fails with exit code 1 when no matches — that's normal (no children)
|
// ps returns exit code 1 when no matches — normal (no children)
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user