perf: skip subagent summary before worker bootstrap

Move the agentId short-circuit above ensureWorkerRunning() so a Stop
hook fired inside a subagent does not trigger worker startup just to
return early. Addresses CodeRabbit nit on summarize.ts:36-47.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-04-19 14:49:43 -07:00
parent b88d1c0c69
commit 1dbbfe32e6
2 changed files with 10 additions and 9 deletions

View File

@@ -962,7 +962,7 @@ View Observations Live @ http://localhost:${i}`:void 0;return{hookSpecificOutput
`),i=!1;for(let s=n.length-1;s>=0;s--){let o=JSON.parse(n[s]);if(o.type===e&&(i=!0,o.message?.content)){let a="",c=o.message.content;if(typeof c=="string")a=c;else if(Array.isArray(c))a=c.filter(u=>u.type==="text").map(u=>u.text).join(`
`);else throw new Error(`Unknown message content format in transcript. Type: ${typeof c}`);return r&&(a=a.replace(ol,""),a=a.replace(/\n{3,}/g,`
`).trim()),a}}return""}var T_,J5=he(()=>{"use strict";T_=require("fs");Q();o_()});var qxe,Hxe,Zxe,hR,gR=he(()=>{"use strict";Or();Q();J5();gn();Si();qxe=sg(fr.DEFAULT),Hxe=500,Zxe=11e4,hR={async execute(t){if(!await or())return{continue:!0,suppressOutput:!0,exitCode:tt.SUCCESS};if(t.agentId)return y.debug("HOOK","Skipping summary: subagent context detected",{sessionId:t.sessionId,agentId:t.agentId,agentType:t.agentType}),{continue:!0,suppressOutput:!0,exitCode:tt.SUCCESS};let{sessionId:r,transcriptPath:n}=t;if(!n)return y.debug("HOOK",`No transcriptPath in Stop hook input for session ${r} - skipping summary`),{continue:!0,suppressOutput:!0,exitCode:tt.SUCCESS};let i="";try{i=K5(n,"assistant",!0)}catch(u){return y.warn("HOOK",`Stop hook: failed to extract last assistant message for session ${r}: ${u instanceof Error?u.message:u}`),{continue:!0,suppressOutput:!0,exitCode:tt.SUCCESS}}if(!i||!i.trim())return y.debug("HOOK","No assistant message in transcript - skipping summary",{sessionId:r,transcriptPath:n}),{continue:!0,suppressOutput:!0,exitCode:tt.SUCCESS};y.dataIn("HOOK","Stop: Requesting summary",{hasLastAssistantMessage:!!i});let s=St(t.platform);if(!(await ut("/api/sessions/summarize",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentSessionId:r,last_assistant_message:i,platformSource:s}),timeoutMs:qxe})).ok)return{continue:!0,suppressOutput:!0};y.debug("HOOK","Summary request queued, waiting for completion");let a=Date.now(),c=null;for(;Date.now()-a<Zxe;){await new Promise(u=>setTimeout(u,Hxe));try{let u=await ut(`/api/sessions/status?contentSessionId=${encodeURIComponent(r)}`,{timeoutMs:5e3}),l=await u.json();if((l.queueLength??0)===0&&u.status!==404){c=l.summaryStored??null,y.info("HOOK","Summary processing complete",{waitedMs:Date.now()-a,summaryStored:c}),c===!1&&y.warn("HOOK","Summary was not stored: LLM response likely lacked valid <summary> tags (#1633)",{sessionId:r,waitedMs:Date.now()-a});break}}catch{}}try{await ut("/api/sessions/complete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentSessionId:r}),timeoutMs:1e4}),y.info("HOOK","Session completed in Stop hook",{contentSessionId:r})}catch(u){y.warn("HOOK",`Stop hook: session-complete failed: ${u instanceof Error?u.message:u}`)}return{continue:!0,suppressOutput:!0}}}});var X5,vR,yR=he(()=>{"use strict";X5=require("path");Or();gn();vR={async execute(t){if(!await or())return{exitCode:tt.SUCCESS};let r=Qr(),n=(0,X5.basename)(t.cwd??process.cwd()),i=t.platform==="claude-code"?"&colors=true":"";try{let s=await ut(`/api/context/inject?project=${encodeURIComponent(n)}${i}`);if(!s.ok)return{exitCode:tt.SUCCESS};let o=await s.text();process.stderr.write(`
`).trim()),a}}return""}var T_,J5=he(()=>{"use strict";T_=require("fs");Q();o_()});var qxe,Hxe,Zxe,hR,gR=he(()=>{"use strict";Or();Q();J5();gn();Si();qxe=sg(fr.DEFAULT),Hxe=500,Zxe=11e4,hR={async execute(t){if(t.agentId)return y.debug("HOOK","Skipping summary: subagent context detected",{sessionId:t.sessionId,agentId:t.agentId,agentType:t.agentType}),{continue:!0,suppressOutput:!0,exitCode:tt.SUCCESS};if(!await or())return{continue:!0,suppressOutput:!0,exitCode:tt.SUCCESS};let{sessionId:r,transcriptPath:n}=t;if(!n)return y.debug("HOOK",`No transcriptPath in Stop hook input for session ${r} - skipping summary`),{continue:!0,suppressOutput:!0,exitCode:tt.SUCCESS};let i="";try{i=K5(n,"assistant",!0)}catch(u){return y.warn("HOOK",`Stop hook: failed to extract last assistant message for session ${r}: ${u instanceof Error?u.message:u}`),{continue:!0,suppressOutput:!0,exitCode:tt.SUCCESS}}if(!i||!i.trim())return y.debug("HOOK","No assistant message in transcript - skipping summary",{sessionId:r,transcriptPath:n}),{continue:!0,suppressOutput:!0,exitCode:tt.SUCCESS};y.dataIn("HOOK","Stop: Requesting summary",{hasLastAssistantMessage:!!i});let s=St(t.platform);if(!(await ut("/api/sessions/summarize",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentSessionId:r,last_assistant_message:i,platformSource:s}),timeoutMs:qxe})).ok)return{continue:!0,suppressOutput:!0};y.debug("HOOK","Summary request queued, waiting for completion");let a=Date.now(),c=null;for(;Date.now()-a<Zxe;){await new Promise(u=>setTimeout(u,Hxe));try{let u=await ut(`/api/sessions/status?contentSessionId=${encodeURIComponent(r)}`,{timeoutMs:5e3}),l=await u.json();if((l.queueLength??0)===0&&u.status!==404){c=l.summaryStored??null,y.info("HOOK","Summary processing complete",{waitedMs:Date.now()-a,summaryStored:c}),c===!1&&y.warn("HOOK","Summary was not stored: LLM response likely lacked valid <summary> tags (#1633)",{sessionId:r,waitedMs:Date.now()-a});break}}catch{}}try{await ut("/api/sessions/complete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentSessionId:r}),timeoutMs:1e4}),y.info("HOOK","Session completed in Stop hook",{contentSessionId:r})}catch(u){y.warn("HOOK",`Stop hook: session-complete failed: ${u instanceof Error?u.message:u}`)}return{continue:!0,suppressOutput:!0}}}});var X5,vR,yR=he(()=>{"use strict";X5=require("path");Or();gn();vR={async execute(t){if(!await or())return{exitCode:tt.SUCCESS};let r=Qr(),n=(0,X5.basename)(t.cwd??process.cwd()),i=t.platform==="claude-code"?"&colors=true":"";try{let s=await ut(`/api/context/inject?project=${encodeURIComponent(n)}${i}`);if(!s.ok)return{exitCode:tt.SUCCESS};let o=await s.text();process.stderr.write(`
`+String.fromCodePoint(128221)+` Claude-Mem Context Loaded

View File

@@ -26,17 +26,11 @@ const MAX_WAIT_FOR_SUMMARY_MS = 110_000; // 110s — fits within Stop hook's 120
export const summarizeHandler: EventHandler = {
async execute(input: NormalizedHookInput): Promise<HookResult> {
// Ensure worker is running before any other logic
const workerReady = await ensureWorkerRunning();
if (!workerReady) {
// Worker not available - skip summary gracefully
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
}
// Skip summaries in subagent context — subagents do not own the session summary.
// Gate on agentId only: that field is present exclusively for Task-spawned subagents.
// agentType alone (no agentId) indicates `--agent`-started main sessions, which still
// own their summary.
// own their summary. Do this BEFORE ensureWorkerRunning() so a subagent Stop hook
// does not bootstrap the worker.
if (input.agentId) {
logger.debug('HOOK', 'Skipping summary: subagent context detected', {
sessionId: input.sessionId,
@@ -46,6 +40,13 @@ export const summarizeHandler: EventHandler = {
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
}
// Ensure worker is running before any other logic
const workerReady = await ensureWorkerRunning();
if (!workerReady) {
// Worker not available - skip summary gracefully
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
}
const { sessionId, transcriptPath } = input;
// Validate required fields before processing