Skip to content

fix(providers): suppress idle watchdog during in-flight Copilot tool calls - #490

Merged
Jason Robert (jrob5756) merged 2 commits into
mainfrom
fix/488-idle-watchdog-tool-calls
Aug 24, 2026
Merged

fix(providers): suppress idle watchdog during in-flight Copilot tool calls#490
Jason Robert (jrob5756) merged 2 commits into
mainfrom
fix/488-idle-watchdog-tool-calls

Conversation

@jrob5756

@jrob5756Jason Robert (jrob5756) commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

The Copilot SDK does not guarantee any events during a tool call —
tool.execution_progress and tool.execution_partial_result exist in the
SDK schema but are opt-in per tool, so for most tool calls nothing arrives
between tool.execution_start and tool.execution_complete. A stale idle
clock 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_content is
last-message-wins), turning a healthy run into a non-retryable failure.

In-flight tool calls (tracked by tool_call_id) now suppress idle
recovery entirely while any remain outstanding; max_session_seconds is
the sole backstop for a genuinely wedged tool (max_agent_iterations
cannot help here — its counter only advances on a new
tool.execution_start, so it's frozen for the entire duration of a
wedge). 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 another
still-in-flight tool) on tool.execution_complete instead of only ever
being set. The first occurrence of extended suppression during a session
is logged at warning level; further occurrences are debug-only.

Also 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

Testing

  • Added tests/test_providers/test_idle_recovery.py covering
    in-flight tool suppression, configurable timeouts, an end-to-end
    regression test for the response-clobbering symptom, and the
    warn-once suppression latch.
  • Updated test_schema.py, test_factory.py, test_registry.py for
    the new runtime config fields.

Jason Robertand others added 2 commits August 24, 2026 12:40
…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>
@jrob5756
Jason Robert (jrob5756) marked this pull request as ready for review August 24, 2026 17:32
@jrob5756
Jason Robert (jrob5756) merged commit 812a7dc into mainAug 24, 2026
12 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(provider): idle watchdog fires during long-running tool calls, injecting spurious recovery prompts and failing healthy runs

1 participant

@jrob5756