Skip to content

fix: prevent CancellationError from killing ask_user tool loop#74

Merged
4regab merged 2 commits into4regab:mainfrom
sgaabdu4:bug/fix-askuser-cancellation-kills-loop
Mar 28, 2026
Merged

fix: prevent CancellationError from killing ask_user tool loop#74
4regab merged 2 commits into4regab:mainfrom
sgaabdu4:bug/fix-askuser-cancellation-kills-loop

Conversation

@sgaabdu4
Copy link
Copy Markdown
Collaborator

Problem

When ask_user is called by the LLM and then internally superseded (e.g., a new request arrives while a previous one is pending), the TaskSync extension was throwing vscode.CancellationError. This caused VS Code's Copilot Chat agent mode to permanently stop calling ask_user after just two cancellations.

Why this happens

VS Code's ToolCallingLoop treats CancellationError from a tool as a hard stop signal at three levels:

  1. buildPrompt2() — checks ToolResultMetadata.isCancelled on all tool results. If any is cancelled, throws CancellationError
  2. _runLoop() catch block — catches CancellationError and breaks out of the loop immediately
  3. shouldAutoRetry() — excludes ChatFetchResponseType.Canceled from retry eligibility

The result: after two superseded ask_user calls, the conversation history contained two cancelled tool results, and the LLM learned that ask_user is broken and stopped calling it.

This is the same class of issue described in microsoft/vscode#241039"Cancelled tool call should not cancel the whole chat session, but rather respond to LLM with an error/special message for this specific tool call, and allow LLM to reason based on the received results."

Solution

1. Return normal results for superseded requests (tools.ts)

Instead of throwing CancellationError when a request is internally superseded, return the cancelled message as a normal AskUserToolResult. The LLM sees the text and can reason about it and call ask_user again.

Real user cancellation (Stop button) still correctly throws CancellationError via the CancellationToken / createCancellationPromise race path.

2. Instruct the LLM to re-ask the same question (toolCallHandler.ts)

Updated the superseded message from a generic "[CANCELLED: New request superseded this one]" to a directive that tells the LLM to re-ask the exact same question, reducing the chance of the question/summary changing on retry.

3. Tests updated (tools.test.ts)

  • Updated 2 existing tests to assert normal result return instead of CancellationError throw
  • Added new test verifying real token cancellation still throws CancellationError

Files Changed

File Change
src/tools.ts Return normal result for superseded requests instead of throwing CancellationError
src/webview/toolCallHandler.ts Updated superseded message to instruct LLM to re-ask same question
src/tools.test.ts Updated existing tests + added new test for real token cancellation

Validation

  • Build: ✅
  • TypeScript (tsc --noEmit): ✅ 0 errors
  • Tests (vitest run): ✅ 437 tests passing
  • Lint (biome check): ✅ 0 issues
  • Code quality (check-code): ✅ All checks pass

References

When ask_user was internally superseded (e.g., a new request arrived while
a previous one was pending), the extension threw vscode.CancellationError.
VS Code's ToolCallingLoop treats CancellationError as a hard stop at three
levels: buildPrompt2 isCancelled check, _runLoop catch/break, and
shouldAutoRetry exclusion. After two cancellations the LLM stopped calling
ask_user entirely.

Changes:
- tools.ts: return a normal AskUserToolResult for superseded requests
  instead of throwing CancellationError. Real token cancellation (Stop
  button) still throws correctly via createCancellationPromise.
- toolCallHandler.ts: update superseded message to instruct the LLM to
  re-ask the exact same question on retry.
- tools.test.ts: update existing tests and add new test for real token
  cancellation still throwing CancellationError.
Copilot AI review requested due to automatic review settings March 28, 2026 04:24
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@sgaabdu4
Copy link
Copy Markdown
Collaborator Author

@n-r-w I created this PR as throw new vscode.CancellationError(); was killing ask_user tool in tools.ts from the changes yesterday. Would like your opinion as well @4regab

@sgaabdu4
Copy link
Copy Markdown
Collaborator Author

@4regab is 3 PRs good enough for a merge haha?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents internally superseded ask_user calls from throwing vscode.CancellationError, which can terminate VS Code Copilot Chat’s tool-calling loop. This keeps the agent/tool loop resilient to overlapping ask_user requests while preserving real user-triggered cancellation behavior via the CancellationToken.

Changes:

  • Return a normal AskUserToolResult (instead of throwing CancellationError) when a pending ask_user request is superseded.
  • Update the superseded/cancelled message returned from the webview handler to instruct the LLM to re-ask the exact same question.
  • Update/add Vitest coverage around superseded vs real token cancellation behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
tasksync-chat/src/tools.ts Changes superseded handling to return a normal tool result instead of throwing CancellationError.
tasksync-chat/src/webview/toolCallHandler.ts Updates the superseded/cancelled response text returned to the tool call.
tasksync-chat/src/tools.test.ts Updates existing cancellation tests and adds a new token-cancellation test case.

@4regab
Copy link
Copy Markdown
Owner

4regab commented Mar 28, 2026

@4regab is 3 PRs good enough for a merge haha?

Definitely! will review the PRs today

… cancellation test

Address PR review comments:
- Extract superseded message to shared constant in remoteConstants.ts (DRY)
- Import constant in toolCallHandler.ts and tools.test.ts
- Add test for mid-flight token cancellation (createCancellationPromise race path)
@4regab
Copy link
Copy Markdown
Owner

4regab commented Mar 28, 2026

@n-r-w I created this PR as throw new vscode.CancellationError(); was killing ask_user tool in tools.ts from the changes yesterday. Would like your opinion as well @4regab

I haven’t personally encountered this yet, so I’ll need to test it more thoroughly later.

@4regab 4regab merged commit dd2e1a8 into 4regab:main Mar 28, 2026
1 check passed
Sign up for free to 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.

3 participants