fix(todo-continuation): remove activity-based stagnation bypass

Activity signals (tool calls like compress, grep, bash) were treated as
'progress' by the stagnation detector, resetting the stagnation counter
every cycle. This prevented MAX_STAGNATION_COUNT from being reached,
causing infinite continuation loops when models degrade to minimal
responses in long sessions (e.g. GLM-5.1 at ~100K tokens).

Stagnation now only tracks actual todo state changes: incomplete count
decrease, completed count increase, or todo snapshot change. Tool-level
activity no longer resets the stagnation counter.
This commit is contained in:
Evgeny Zotov
2026-04-10 20:07:56 +02:00
committed by YeonGyu-Kim
parent fe66c96215
commit 68e9d54fa5
6 changed files with 7 additions and 78 deletions
@@ -144,9 +144,9 @@ describe("createSessionStateStore", () => {
expect(stagnatedAgainUpdate.stagnationCount).toBe(1)
})
test("given non-codex activity happens after a successful continuation, treats it as progress", () => {
test("given tool activity happens after a successful continuation without todo changes, keeps counting stagnation", () => {
// given
const sessionID = "ses-non-codex-activity-progress"
const sessionID = "ses-activity-stagnation"
const state = sessionStateStore.getState(sessionID)
const todos = [
{ id: "1", content: "Task 1", status: "pending", priority: "high" },
@@ -154,40 +154,12 @@ describe("createSessionStateStore", () => {
sessionStateStore.trackContinuationProgress(sessionID, 1, todos)
state.awaitingPostInjectionProgressCheck = true
sessionStateStore.recordActivity(sessionID)
// when
const progressUpdate = sessionStateStore.trackContinuationProgress(
sessionID,
1,
todos,
{ allowActivityProgress: true },
)
// then
expect(progressUpdate.hasProgressed).toBe(true)
expect(progressUpdate.progressSource).toBe("activity")
expect(progressUpdate.stagnationCount).toBe(0)
})
test("given codex activity happens after a successful continuation, keeps counting stagnation", () => {
// given
const sessionID = "ses-codex-activity-stagnation"
const state = sessionStateStore.getState(sessionID)
const todos = [
{ id: "1", content: "Task 1", status: "pending", priority: "high" },
]
sessionStateStore.trackContinuationProgress(sessionID, 1, todos)
state.awaitingPostInjectionProgressCheck = true
sessionStateStore.recordActivity(sessionID)
// when
const progressUpdate = sessionStateStore.trackContinuationProgress(
sessionID,
1,
todos,
{ allowActivityProgress: false },
)
// then