Skip to content

.NET: [Bug]: Follow-up to #8575: A failed approval-resume run permanently corrupts the session #8849

Closed

Description

Description

Hi westey (@westey-m), and thank you for investigating issue #8575. I saw that your fix has been included in version 1.23.0, and I can confirm that approval can now be re-sent after a failed resume. However, there is still a remaining issue, so I'm opening this new issue to give it visibility and better define the failing scenario.

The session can still become permanently unusable when the resume run is never executed. For example, the host fails before calling RunAsync with the approval response: a UI/transport error, the user closing the page, a validation exception, or a process restart before the user answers. In that case every later run on the session still throws:

var response = await agent.RunAsync("What time is it?", session);  // approval requested + persisted
Console.WriteLine($"Approval requests: {GetApprovalRequests(response).Count}");

try
{
    // Anything that fails before the approval response is sent back to the agent.
    throw new InvalidOperationException("Something failed before sending the approval response.");
}
catch (Exception ex)
{
    Console.WriteLine($"Host error: {ex.Message}");
}

response = await agent.RunAsync("Hello, tell me a joke.", session);  // throws, and keeps throwing

Output:

Approval requests: 1
Host error: Something failed before sending the approval response.
System.InvalidOperationException: ToolApprovalRequestContent found with FunctionCall.CallId(s) 'call_pEtu9eNwav5SnGKRORXFtzyB' that have no matching ToolApprovalResponseContent.

So the underlying problem remains. A pending ToolApprovalRequestContent in the persisted history blocks every new user message. The host has no public way to enumerate or close it (#7867, #7872), unless it still holds the original request objects in memory. Could the framework handle a dangling request from history as a recoverable state? For example, it could auto-reject it with a synthetic response when a new user message arrives, or re-surface it to the caller, rather than throwing.

Package Versions

Microsoft.Agents.AI.OpenAI: 1.23.0

.NET Version

.NET 10

Additional Context

No response

Activity

  1. added
    reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
    on Sep 29, 2026
  2. github-actions commented on Sep 29, 2026

    @github-actions
    Contributor

    🤖 Automated triage reproduction notes (agent-authored — trust but verify)

    Agent analysis

    Repro: dotnet/src/Microsoft.Agents.AI/ChatClient/ApprovalResponseBindingChatClient.cs::ValidateInboundApprovalResponses leaves persisted unanswered approval requests unchanged when a new turn contains no approval response; forwarding at line 90 then triggers the unmatched-request exception in FunctionInvokingChatClient. Trigger this by producing an approval request, serializing and deserializing the session without responding, then calling RunAsync with an unrelated user message. The minimal failing test is RunAsync_WhenApprovalResponseIsAbandoned_RestoredSessionCanProceedAsync.

    • Failing test: dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ApprovalRetryTests.cs
    • Files examined: dotnet/src/Microsoft.Agents.AI/ChatClient/ApprovalResponseBindingChatClient.cs, dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs, dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ApprovalRetryTests.cs, dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ApprovalsTests.cs, dotnet/nuget/nuget-package.props
    • Tests run: ChatClientAgent_ApprovalRetryTests (4 passed baseline), RunAsync_WhenApprovalResponseIsAbandoned_RestoredSessionCanProceedAsync (failed)
    • Reported version: 1.23.0
    • Current version: 1.23.0
  3. added
    agentsUsage: [Issues, PRs], Target: Single agent
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Sep 29, 2026
  4. westey-m commented on Oct 1, 2026

    @westey-m
    Collaborator

    Could the framework handle a dangling request from history as a recoverable state? For example, it could auto-reject it with a synthetic response when a new user message arrives, or re-surface it to the caller, rather than throwing.

    Hi Marco Minerva (@marcominerva), thanks for logging the issue. Yes, I think auto-rejecting probably makes sense. I tried resurfacing elsewhere, but it becomes very complex very quickly, and if someone didn't provide a response deliberately I think it's reasonable to treat that as a rejection.

  5. marcominerva commented on Oct 1, 2026

    @marcominerva
    ContributorAuthor

    I agree with implementing auto-reject. While addressing it this way, I wonder if we should also take this other issue into consideration for the auto-reject behavior: #8503.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .NetagentsUsage: [Issues, PRs], Target: Single agentreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions