Session becomes permanently stuck with repeating CAPIError: 400 after interrupted tool execution (dangling tool_use without tool_result)
Environment:
Product: GitHub Copilot app (desktop), version 1.0.25
OS: Windows (Windows_NT)
Model: Claude Sonnet 5
Mode: Interactive, agent: dev-machine-setup (custom agent context)
Summary:
After the app runs for a while, a session can enter a broken state where every subsequent turn — including clicking "continue" — fails immediately with a 400 error from the backend, instead of resuming normally. The session becomes permanently unusable and must be abandoned.
Error message (exact):
Execution failed: CAPIError: 400 messages.42: tool_use ids were found without tool_result blocks immediately after: toolu_01QsFgnU6KfxALW7z866PMKD. Each tool_use block must have a corresponding tool_result block in the next message. (Request ID: C584:232FD6:363FAD2:3994B9F:6A60D0A3)
A near-identical error also occurred moments earlier with a different Request ID:
Execution failed: CAPIError: 400 messages.42: tool_use ids were found without tool_result blocks immediately after: toolu_01QsFgnU6KfxALW7z866PMKD. (Request ID: C584:232FD6:363E1FD:39931EA:6A60D0A0)
An earlier, possibly related error appeared in the same session shortly before these:
Execution failed: Error: Invalid hook session handle: 2
Steps to reproduce (best understanding):
Start a long-running interactive session in the Copilot app (in this case, running the dev-machine-setup custom agent — installing Python via a no-admin per-user installer, required by gcloud CLI).
Let the session run for an extended period with multiple tool calls (shell commands, file edits, etc.).
At some point (possibly a dropped connection, a tool call that didn't complete cleanly, or an internal timeout), the session's message history ends up with a tool_use block (id toolu_01QsFgnU6KfxALW7z866PMKD) that has no matching tool_result in the following message.
From this point on, every turn (including pressing "continue") resends the same malformed history to the backend, which rejects it with a 400 error, since the API requires every tool_use to be immediately followed by its tool_result.
The session never recovers on its own — restarting the app and resuming the same session reproduces the exact same error.
Expected behavior:
The app should never send a message history with a dangling tool_use lacking its tool_result.
If a tool call is interrupted (client crash, dropped connection, app restart) before its result is recorded, the app should detect the incomplete pair on reload/resume and either:
synthesize a placeholder tool_result (e.g., "execution interrupted/unknown result") for the orphaned tool_use, or
drop/truncate the incomplete trailing turn before resending history to the model.
At minimum, restarting the app and resuming the session should self-heal this state, rather than reproducing the same 400 error indefinitely.
Actual behavior:
The session's stored history is corrupted with a dangling tool_use.
Restarting the app and resuming the same session does not fix it — the same broken history is resent and the same 400 error recurs (confirmed twice, with two different Request IDs, seconds apart).
The only workaround found is to abandon the session entirely and start a new one, manually copying over context.
Impact:
Total loss of the affected session's ability to continue — no in-app recovery path.
Any long-running task (multi-step machine setup, installs, agent workflows) risks becoming unrecoverable if interrupted at the wrong moment.
Additional context / Request IDs for correlation:
C584:232FD6:363E1FD:39931EA:6A60D0A0
C584:232FD6:363FAD2:3994B9F:6A60D0A3
Offending tool_use id: toolu_01QsFgnU6KfxALW7z866PMKD
Suggested fix:
Add a history-sanitization/repair step when loading or resuming a session (and ideally right before sending any request to the model): scan for tool_use blocks without an immediately following tool_result block, and either inject a synthetic error tool_result or truncate the trailing incomplete turn, before the history is sent upstream.
| Field |
Value |
| App version |
1.0.25 |
| OS |
Windows 10.0.26200 |
| Theme |
Tokyo Night |
| Path |
/chat |
| Tenure |
Week 2 |
Session becomes permanently stuck with repeating CAPIError: 400 after interrupted tool execution (dangling tool_use without tool_result)
Environment:
Product: GitHub Copilot app (desktop), version 1.0.25
OS: Windows (Windows_NT)
Model: Claude Sonnet 5
Mode: Interactive, agent: dev-machine-setup (custom agent context)
Summary:
After the app runs for a while, a session can enter a broken state where every subsequent turn — including clicking "continue" — fails immediately with a 400 error from the backend, instead of resuming normally. The session becomes permanently unusable and must be abandoned.
Error message (exact):
Execution failed: CAPIError: 400 messages.42:
tool_useids were found withouttool_resultblocks immediately after: toolu_01QsFgnU6KfxALW7z866PMKD. Eachtool_useblock must have a correspondingtool_resultblock in the next message. (Request ID: C584:232FD6:363FAD2:3994B9F:6A60D0A3)A near-identical error also occurred moments earlier with a different Request ID:
Execution failed: CAPIError: 400 messages.42:
tool_useids were found withouttool_resultblocks immediately after: toolu_01QsFgnU6KfxALW7z866PMKD. (Request ID: C584:232FD6:363E1FD:39931EA:6A60D0A0)An earlier, possibly related error appeared in the same session shortly before these:
Execution failed: Error: Invalid hook session handle: 2
Steps to reproduce (best understanding):
Start a long-running interactive session in the Copilot app (in this case, running the dev-machine-setup custom agent — installing Python via a no-admin per-user installer, required by gcloud CLI).
Let the session run for an extended period with multiple tool calls (shell commands, file edits, etc.).
At some point (possibly a dropped connection, a tool call that didn't complete cleanly, or an internal timeout), the session's message history ends up with a tool_use block (id toolu_01QsFgnU6KfxALW7z866PMKD) that has no matching tool_result in the following message.
From this point on, every turn (including pressing "continue") resends the same malformed history to the backend, which rejects it with a 400 error, since the API requires every tool_use to be immediately followed by its tool_result.
The session never recovers on its own — restarting the app and resuming the same session reproduces the exact same error.
Expected behavior:
The app should never send a message history with a dangling tool_use lacking its tool_result.
If a tool call is interrupted (client crash, dropped connection, app restart) before its result is recorded, the app should detect the incomplete pair on reload/resume and either:
synthesize a placeholder tool_result (e.g., "execution interrupted/unknown result") for the orphaned tool_use, or
drop/truncate the incomplete trailing turn before resending history to the model.
At minimum, restarting the app and resuming the session should self-heal this state, rather than reproducing the same 400 error indefinitely.
Actual behavior:
The session's stored history is corrupted with a dangling tool_use.
Restarting the app and resuming the same session does not fix it — the same broken history is resent and the same 400 error recurs (confirmed twice, with two different Request IDs, seconds apart).
The only workaround found is to abandon the session entirely and start a new one, manually copying over context.
Impact:
Total loss of the affected session's ability to continue — no in-app recovery path.
Any long-running task (multi-step machine setup, installs, agent workflows) risks becoming unrecoverable if interrupted at the wrong moment.
Additional context / Request IDs for correlation:
C584:232FD6:363E1FD:39931EA:6A60D0A0
C584:232FD6:363FAD2:3994B9F:6A60D0A3
Offending tool_use id: toolu_01QsFgnU6KfxALW7z866PMKD
Suggested fix:
Add a history-sanitization/repair step when loading or resuming a session (and ideally right before sending any request to the model): scan for tool_use blocks without an immediately following tool_result block, and either inject a synthetic error tool_result or truncate the trailing incomplete turn, before the history is sent upstream.