Uh oh!
There was an error while loading. Please reload this page.
fix(providers): suppress idle watchdog during in-flight Copilot tool calls - #490
Merged
Merged
Conversation
…calls The Copilot SDK emits no events between tool.execution_start and tool.execution_complete, so a stale idle clock during a long-running tool call was indistinguishable from a genuinely stuck session, triggering a spurious "please continue" recovery prompt mid tool-call that could overwrite the agent's eventual structured output. In-flight tool calls (tracked by tool_call_id) now suppress idle recovery entirely while any remain outstanding; max_session_seconds / max_agent_iterations remain the backstop for a genuinely wedged tool. last_activity_ref's tool name is cleared (or rolled to another still-in-flight tool) on tool.execution_complete instead of only ever being set. Adds configurable runtime.idle_timeout_seconds and runtime.max_idle_recovery_attempts (Copilot-only) so workflows with legitimately long tool calls can tune the watchdog. Closes#488
…ression Blocking fixes (#488): - Remove the "pop the oldest entry" fallback in the tool.execution_complete handler that could evict a different, still-running tool's active_tools entry on a duplicate/unmatched event, re-arming the watchdog mid-tool-call and reproducing #488 while appearing fixed. Replaced with a non-mutating debug log; max_session_seconds remains the backstop for a stale entry. - Strengthen TestOnEventActiveTools assertions so both tests actually pin the fix (verified to fail against the pre-fix provider, pass post-fix). - Add an end-to-end regression test driving a real tool.execution_start -> silence -> tool.execution_complete sequence through _send_and_wait, asserting the recovery prompt never clobbers response_content. - Add a warn-once latch (mirroring _context_window_anomaly_warned) so the first occurrence of extended idle-recovery suppression during a session is logged at warning level (console + logger), instead of silently degrading a previously console-visible 90s warning into up to 31.5 minutes of total silence. Recommendations applied: - Corrected the repeated false claim that the SDK "emits no events" during a tool call (it does not guarantee any, but tool.execution_progress / tool.execution_partial_result exist and are opt-in) across copilot.py, schema.py, docs/configuration.md, CHANGELOG.md, and the PR description; consolidated the rationale into one canonical docstring. - Corrected the inaccurate claim that max_agent_iterations backstops a wedged tool call (its counter only advances on tool.execution_start, so it's frozen for the whole wedge) — max_session_seconds is the sole backstop. - Added IdleRecoveryConfig.__post_init__ validation so directly-constructed configs (bypassing the Pydantic schema bounds) can't produce an unbounded busy-wait loop. - Simplified factory.py's IdleRecoveryConfig construction to a dict-filter + single constructor call instead of a three-way ternary per field. - Added ProviderCapabilities.idle_recovery (Copilot-only) with a workflow-level validator warning (not an error, since these are tuning knobs rather than safety bounds) when idle_timeout_seconds / max_idle_recovery_attempts are set against a provider that ignores them. - Bounded two previously-unbounded busy-wait test loops with asyncio.wait_for(..., timeout=5.0). - Added an overlapping-tool-calls end-to-end test keyed on tool_call_id (not tool_name), verified to reproduce the hang if the dict were mistakenly keyed by tool name instead. - Documented the max_session_seconds backstop in docs/configuration.md so a legitimately long tool call doesn't silently exceed it unexpectedly. Skipped: ACA forwarding of the two idle-recovery fields (larger, separate scope spanning factory/aca/aca_runner) and AGENTS.md documentation update (the two runtime knobs and active_tools mechanism are already documented in the config docs and code comments; deferring to keep this diff scoped to the review findings). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Jason Robert (jrob5756)
marked this pull request as ready for review
August 24, 2026 17:32
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Copilot SDK does not guarantee any events during a tool call —
tool.execution_progressandtool.execution_partial_resultexist in theSDK schema but are opt-in per tool, so for most tool calls nothing arrives
between
tool.execution_startandtool.execution_complete. A stale idleclock during a long-running tool call was previously indistinguishable
from a genuinely stuck session — triggering a spurious "please continue"
recovery prompt mid tool-call. That prompt's conversational reply then
overwrote the agent's eventual structured output (
response_contentislast-message-wins), turning a healthy run into a non-retryable failure.
In-flight tool calls (tracked by
tool_call_id) now suppress idlerecovery entirely while any remain outstanding;
max_session_secondsisthe sole backstop for a genuinely wedged tool (
max_agent_iterationscannot help here — its counter only advances on a new
tool.execution_start, so it's frozen for the entire duration of awedge). Recovery-prompt and stuck-session messages also no longer
misattribute the failure to a tool that has already completed —
last_activity_ref's tool name is now cleared (or rolled to anotherstill-in-flight tool) on
tool.execution_completeinstead of only everbeing set. The first occurrence of extended suppression during a session
is logged at
warninglevel; further occurrences are debug-only.Also adds configurable
runtime.idle_timeout_secondsandruntime.max_idle_recovery_attempts(Copilot-only) so workflows withlegitimately long tool calls can tune the watchdog.
Closes#488
Testing
tests/test_providers/test_idle_recovery.pycoveringin-flight tool suppression, configurable timeouts, an end-to-end
regression test for the response-clobbering symptom, and the
warn-once suppression latch.
test_schema.py,test_factory.py,test_registry.pyforthe new runtime config fields.