diff --git a/hooks/claude-code/handler.js b/hooks/claude-code/handler.js index 413ef13..9eef47f 100755 --- a/hooks/claude-code/handler.js +++ b/hooks/claude-code/handler.js @@ -201,6 +201,36 @@ function clearState(sessionKey) { } catch { } } +function readProcCmdline(pid) { + try { + return readFileSync(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ").trim(); + } catch { + return ""; + } +} +function readProcPPID(pid) { + try { + const status = readFileSync(`/proc/${pid}/status`, "utf8"); + const match = status.match(/^PPid:\s+(\d+)$/m); + return match ? Number(match[1]) : void 0; + } catch { + return void 0; + } +} +function getProcessTree() { + const tree = []; + let pid = process.pid; + for (let i = 0; pid && i < 8; i++) { + tree.push({ pid, cmd: readProcCmdline(pid) }); + pid = readProcPPID(pid); + } + return tree; +} +function isNonPersistentClaudeLaunch() { + return getProcessTree().some( + ({ cmd }) => cmd.includes("/claude") && cmd.includes("--no-session-persistence") + ); +} var activeRuns = /* @__PURE__ */ new Map(); var activeSpans = /* @__PURE__ */ new Map(); var activeSubagents = /* @__PURE__ */ new Map(); @@ -275,8 +305,18 @@ async function handleSessionStart(input) { return; } const hookEventName = pickString(input.hook_event_name); - if (hookEventName && hookEventName !== "SessionStart") { - console.error(`[agentmon] ignoring claude-code session.start with hook_event_name=${hookEventName}`); + if (hookEventName !== "SessionStart") { + console.error(`[agentmon] ignoring claude-code session.start with hook_event_name=${hookEventName || "missing"}`); + return; + } + const cwd = pickString(input.cwd); + const transcriptPath = pickString(input.transcript_path); + if (!cwd || !transcriptPath) { + console.error("[agentmon] ignoring claude-code session.start without cwd or transcript_path"); + return; + } + if (pickString(input.source) === "startup" && isNonPersistentClaudeLaunch()) { + console.error("[agentmon] ignoring claude-code startup from --no-session-persistence launch"); return; } const runId = randomUUID2(); @@ -284,7 +324,12 @@ async function handleSessionStart(input) { saveState(sessionKey, { runId, spans: {} }); const contextWindow = getContextWindow(input); enqueue(buildEnvelope(FRAMEWORK, HOST, "session.start", sessionKey, { - attributes: contextWindow ? { context_window: contextWindow } : void 0 + attributes: { + cwd, + transcript_path: transcriptPath, + source: pickString(input.source), + ...contextWindow && { context_window: contextWindow } + } })); enqueue(buildEnvelope(FRAMEWORK, HOST, "run.start", sessionKey, { runId, diff --git a/hooks/claude-code/handler.ts b/hooks/claude-code/handler.ts index e0aa25e..94d40df 100644 --- a/hooks/claude-code/handler.ts +++ b/hooks/claude-code/handler.ts @@ -54,6 +54,41 @@ function saveState(sessionKey: string, state: SessionState) { function clearState(sessionKey: string) { try { unlinkSync(join(STATE_DIR, sessionKey + '.json')); } catch { /* ignore */ } } + +function readProcCmdline(pid: number): string { + try { + return readFileSync(`/proc/${pid}/cmdline`, 'utf8').replace(/\0/g, ' ').trim(); + } catch { + return ''; + } +} + +function readProcPPID(pid: number): number | undefined { + try { + const status = readFileSync(`/proc/${pid}/status`, 'utf8'); + const match = status.match(/^PPid:\s+(\d+)$/m); + return match ? Number(match[1]) : undefined; + } catch { + return undefined; + } +} + +function getProcessTree() { + const tree: Array<{ pid: number; cmd: string }> = []; + let pid: number | undefined = process.pid; + for (let i = 0; pid && i < 8; i++) { + tree.push({ pid, cmd: readProcCmdline(pid) }); + pid = readProcPPID(pid); + } + + return tree; +} + +function isNonPersistentClaudeLaunch() { + return getProcessTree().some(({ cmd }) => + cmd.includes('/claude') && cmd.includes('--no-session-persistence') + ); +} // ───────────────────────────────────────────────────────────────────────────── const activeRuns = new Map(); @@ -131,8 +166,20 @@ async function handleSessionStart(input: Dict) { } const hookEventName = pickString(input.hook_event_name); - if (hookEventName && hookEventName !== 'SessionStart') { - console.error(`[agentmon] ignoring claude-code session.start with hook_event_name=${hookEventName}`); + if (hookEventName !== 'SessionStart') { + console.error(`[agentmon] ignoring claude-code session.start with hook_event_name=${hookEventName || 'missing'}`); + return; + } + + const cwd = pickString(input.cwd); + const transcriptPath = pickString(input.transcript_path); + if (!cwd || !transcriptPath) { + console.error('[agentmon] ignoring claude-code session.start without cwd or transcript_path'); + return; + } + + if (pickString(input.source) === 'startup' && isNonPersistentClaudeLaunch()) { + console.error('[agentmon] ignoring claude-code startup from --no-session-persistence launch'); return; } @@ -143,7 +190,12 @@ async function handleSessionStart(input: Dict) { const contextWindow = getContextWindow(input); enqueue(buildEnvelope(FRAMEWORK, HOST, 'session.start', sessionKey, { - attributes: contextWindow ? { context_window: contextWindow } : undefined, + attributes: { + cwd, + transcript_path: transcriptPath, + source: pickString(input.source), + ...(contextWindow && { context_window: contextWindow }), + }, })); enqueue(buildEnvelope(FRAMEWORK, HOST, 'run.start', sessionKey, {