fix(todo-continuation): remove activity-based stagnation bypass#3319
fix(todo-continuation): remove activity-based stagnation bypass#3319EZotoff wants to merge 2 commits intocode-yeongyu:devfrom
Conversation
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.
There was a problem hiding this comment.
Pull request overview
Removes tool/activity-based “progress” from the todo continuation stagnation detector so stagnation can reliably reach the configured limit and prevent infinite continuation loops.
Changes:
- Removed activity-signal tracking (
recordActivity, counters, and options) from continuation progress tracking. - Simplified progress detection to todo-only signals (incomplete/completed counts and todo snapshot changes).
- Updated callers/tests to align with the new todo-only stagnation behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/hooks/todo-continuation-enforcer/types.ts |
Removes the ContinuationProgressOptions type that enabled activity-based progress. |
src/hooks/todo-continuation-enforcer/session-state.ts |
Drops activity counters and options; progress/stagnation now determined strictly from todo state. |
src/hooks/todo-continuation-enforcer/session-state.test.ts |
Replaces activity-based assertions with a todo-only stagnation test scenario. |
src/hooks/todo-continuation-enforcer/non-idle-events.ts |
Removes recordActivity() calls from non-idle event handling. |
src/hooks/todo-continuation-enforcer/idle-event.ts |
Removes model-based gating for activity progress and stops passing the option into progress tracking. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 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" }, | ||
| ] | ||
|
|
||
| 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 }, | ||
| ) |
There was a problem hiding this comment.
The updated test name mentions "tool activity happens" but the test no longer simulates any tool/non-idle activity (it only calls trackContinuationProgress twice with identical inputs). This makes the intent misleading and overlaps with the earlier stagnation test; consider renaming it to reflect "no todo changes" or explicitly simulating tool activity via handleNonIdleEvent to validate the behavior being described.
There was a problem hiding this comment.
No issues found across 5 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Requires human review: Removing activity-based progress bypass may cause regressions in complex tasks where multiple tool calls are required before a todo can be updated, potentially triggering stagnation too early.
Address review feedback: test name no longer references 'tool activity' since activity tracking was removed.
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Requires human review: Removing activity-based progress bypass might cause regressions by prematurely stopping valid long-running tool sequences (e.g. >3 tool calls) that don't immediately update todos.
Summary
Activity signals (tool calls like
compress,grep,bash) were treated as "progress" by the stagnation detector intrackContinuationProgress(), resettingstagnationCountto 0 every cycle. This preventedMAX_STAGNATION_COUNT(3) from ever being reached, causing infinite continuation loops when models degrade to minimal responses in long sessions.Root Cause
The bug manifests when a model (observed with GLM-5.1 at ~100K tokens) can no longer produce substantive responses but still executes tool calls:
awaitingPostInjectionProgressCheck = trueactivitySignalCountincrementshasObservedExternalActivity = true→progressSource = "activity"→hasProgressed = truestagnationCountreset to 0 — never reaches 3Fix
Removed all activity-based progress detection. Stagnation now only tracks actual todo state changes:
Tool-level activity (compress, grep, bash, etc.) no longer resets the stagnation counter.
Changes
session-state.tsactivitySignalCount,lastObservedActivitySignalCount,recordActivity(),ContinuationProgressOptions. Simplified progress to todo-only.non-idle-events.tsrecordActivity()callsidle-event.tsshouldAllowActivityProgress()andallowActivityProgressoptiontypes.tsContinuationProgressOptionsinterfacesession-state.test.tsTesting
Summary by cubic
Remove activity-based stagnation bypass to stop infinite continuation loops in long sessions. Progress is now detected only from todo changes, not tool calls.
Bug Fixes
Refactors
recordActivity(),ContinuationProgressOptions(andallowActivityProgress), and activity-related state;progressSourceis now "todo" or "none". Renamed the remaining test to reflect todo-only progress checks.Written for commit b610f81. Summary will update on new commits.