fix(todo-continuation-enforcer): check isContinuationStopped in injectContinuation to close /stop-continuation race

fix(todo-continuation-enforcer): check isContinuationStopped in injectContinuation to close /stop-continuation race
This commit is contained in:
YeonGyu-Kim 2026-02-19 14:25:16 +09:00 committed by GitHub
commit fb596ed149
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 36 additions and 0 deletions

View File

@ -37,6 +37,7 @@ export async function injectContinuation(args: {
skipAgents?: string[] skipAgents?: string[]
resolvedInfo?: ResolvedMessageInfo resolvedInfo?: ResolvedMessageInfo
sessionStateStore: SessionStateStore sessionStateStore: SessionStateStore
isContinuationStopped?: (sessionID: string) => boolean
}): Promise<void> { }): Promise<void> {
const { const {
ctx, ctx,
@ -45,6 +46,7 @@ export async function injectContinuation(args: {
skipAgents = DEFAULT_SKIP_AGENTS, skipAgents = DEFAULT_SKIP_AGENTS,
resolvedInfo, resolvedInfo,
sessionStateStore, sessionStateStore,
isContinuationStopped,
} = args } = args
const state = sessionStateStore.getExistingState(sessionID) const state = sessionStateStore.getExistingState(sessionID)
@ -53,6 +55,11 @@ export async function injectContinuation(args: {
return return
} }
if (isContinuationStopped?.(sessionID)) {
log(`[${HOOK_NAME}] Skipped injection: continuation stopped for session`, { sessionID })
return
}
const hasRunningBgTasks = backgroundManager const hasRunningBgTasks = backgroundManager
? backgroundManager.getTasksByParentSession(sessionID).some((task: { status: string }) => task.status === "running") ? backgroundManager.getTasksByParentSession(sessionID).some((task: { status: string }) => task.status === "running")
: false : false

View File

@ -38,6 +38,7 @@ export function startCountdown(args: {
backgroundManager?: BackgroundManager backgroundManager?: BackgroundManager
skipAgents: string[] skipAgents: string[]
sessionStateStore: SessionStateStore sessionStateStore: SessionStateStore
isContinuationStopped?: (sessionID: string) => boolean
}): void { }): void {
const { const {
ctx, ctx,
@ -47,6 +48,7 @@ export function startCountdown(args: {
backgroundManager, backgroundManager,
skipAgents, skipAgents,
sessionStateStore, sessionStateStore,
isContinuationStopped,
} = args } = args
const state = sessionStateStore.getState(sessionID) const state = sessionStateStore.getState(sessionID)
@ -72,6 +74,7 @@ export function startCountdown(args: {
skipAgents, skipAgents,
resolvedInfo, resolvedInfo,
sessionStateStore, sessionStateStore,
isContinuationStopped,
}) })
}, COUNTDOWN_SECONDS * 1000) }, COUNTDOWN_SECONDS * 1000)

View File

@ -187,5 +187,6 @@ export async function handleSessionIdle(args: {
backgroundManager, backgroundManager,
skipAgents, skipAgents,
sessionStateStore, sessionStateStore,
isContinuationStopped,
}) })
} }

View File

@ -1608,6 +1608,31 @@ describe("todo-continuation-enforcer", () => {
expect(promptCalls).toHaveLength(0) expect(promptCalls).toHaveLength(0)
}) })
test("should not inject when isContinuationStopped becomes true during countdown", async () => {
// given - session where continuation is not stopped at idle time but stops during countdown
const sessionID = "main-race-condition"
setMainSession(sessionID)
let stopped = false
const hook = createTodoContinuationEnforcer(createMockPluginInput(), {
isContinuationStopped: () => stopped,
})
// when - session goes idle with continuation not yet stopped
await hook.handler({
event: { type: "session.idle", properties: { sessionID } },
})
// when - stop-continuation fires during the 2s countdown window
stopped = true
// when - countdown elapses and injectContinuation fires
await fakeTimers.advanceBy(3000)
// then - no injection because isContinuationStopped became true before injectContinuation ran
expect(promptCalls).toHaveLength(0)
})
test("should inject when isContinuationStopped returns false", async () => { test("should inject when isContinuationStopped returns false", async () => {
fakeTimers.restore() fakeTimers.restore()
// given - session with continuation not stopped // given - session with continuation not stopped