fix(todo-continuation-enforcer): re-verify todos after countdown to prevent stale data injection (#239)
Fixes the race condition where the todo continuation hook would inject a continuation prompt even when all todos had been completed during the countdown period. The root cause was that executeAfterCountdown() used stale todo data from the initial session.idle check without re-verifying that incomplete todos still existed after the countdown finished. Changes: - Add fresh todo verification in executeAfterCountdown() before prompt injection - Use fresh todo data in the continuation prompt message - Abort injection if no incomplete todos remain after countdown This properly handles the case where: 1. session.idle fires (e.g., user enters shell mode in TUI) 2. Initial check finds incomplete todos, starts countdown 3. During countdown, todos get completed 4. Countdown ends, fresh check detects no incomplete todos 5. Hook aborts instead of injecting stale prompt Fixes #234 Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
This commit is contained in:
@@ -215,6 +215,30 @@ export function createTodoContinuationEnforcer(
|
||||
return
|
||||
}
|
||||
|
||||
let freshTodos: Todo[] = []
|
||||
try {
|
||||
log(`[${HOOK_NAME}] Re-verifying todos after countdown`, { sessionID })
|
||||
const response = await ctx.client.session.todo({
|
||||
path: { id: sessionID },
|
||||
})
|
||||
freshTodos = (response.data ?? response) as Todo[]
|
||||
log(`[${HOOK_NAME}] Fresh todo count`, { sessionID, todosCount: freshTodos?.length ?? 0 })
|
||||
} catch (err) {
|
||||
log(`[${HOOK_NAME}] Failed to re-verify todos`, { sessionID, error: String(err) })
|
||||
return
|
||||
}
|
||||
|
||||
const freshIncomplete = freshTodos.filter(
|
||||
(t) => t.status !== "completed" && t.status !== "cancelled"
|
||||
)
|
||||
|
||||
if (freshIncomplete.length === 0) {
|
||||
log(`[${HOOK_NAME}] Abort: no incomplete todos after countdown`, { sessionID, total: freshTodos.length })
|
||||
return
|
||||
}
|
||||
|
||||
log(`[${HOOK_NAME}] Confirmed incomplete todos, proceeding with injection`, { sessionID, incomplete: freshIncomplete.length, total: freshTodos.length })
|
||||
|
||||
remindedSessions.add(sessionID)
|
||||
|
||||
try {
|
||||
@@ -237,7 +261,7 @@ export function createTodoContinuationEnforcer(
|
||||
parts: [
|
||||
{
|
||||
type: "text",
|
||||
text: `${CONTINUATION_PROMPT}\n\n[Status: ${todos.length - incomplete.length}/${todos.length} completed, ${incomplete.length} remaining]`,
|
||||
text: `${CONTINUATION_PROMPT}\n\n[Status: ${freshTodos.length - freshIncomplete.length}/${freshTodos.length} completed, ${freshIncomplete.length} remaining]`,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user