Skip to content

Flaky: Reload_Picks_Up_Extension_Added_After_Session_Create only tolerates one of two transient post-create errors #2337

Description

@jbayardo

GitHub.Copilot.Test.E2E.RpcExtensionsLoadedE2ETests.Reload_Picks_Up_Extension_Added_After_Session_Create fails intermittently against github/copilot-agent-runtime CI with:

System.IO.IOException : Communication error with Copilot CLI: Request session.extensions.reload failed with message: session not found: <session-id>
---- GitHub.Copilot.RemoteRpcException : Request session.extensions.reload failed with message: session not found: <session-id>
   at GitHub.Copilot.Test.Harness.TestHelper.WaitForConditionAsync(...)
   at RpcExtensionsLoadedE2ETests.Reload_Picks_Up_Extension_Added_After_Session_Create() line 256

Mechanism

The test already knows extension setup is asynchronous after session create, and polls ReloadAsync for that reason:

transientExceptionFilter: ex => ex.ToString().Contains("Extensions not available", StringComparison.OrdinalIgnoreCase),

But the runtime has two distinct transient errors in that same window, not one. session.extensions.reload resolves the service via SessionExtensionsService::for_session(&call.session_id), which returns Result<Option<_>, SessionRegistryError> (src/runtime/src/shared_api/extensions.rs):

SessionExtensionsService::for_session(&call.session_id)
    .map_err(|error| DispatchError::message(error.to_string()))
    .and_then(|service| {
        service.ok_or_else(|| DispatchError::message("Extensions not available"))
    })
  • Err(SessionRegistryError::NotFound)"session not found: {session_id}" — the session isn't in the runtime's global registry yet.
  • Ok(None)"Extensions not available" — the session is registered, but the extensions service isn't installed yet.

Which one you get depends purely on whether session registration has landed by the time the first poll fires. The filter matches only the second, so when registration is the slower of the two, WaitForConditionAsync rethrows on the first attempt rather than retrying — the failing runs show Elapsed=00:00:00.24, RequestId=3, Status=Failed, i.e. it never polled a second time.

Evidence it's a race, not a runtime regression

Within a single workflow run all four backend legs use the same freshly built CLI binary, and the results differ between legs:

  • Run 31840012655: failed on capi, anthropic-messages, openai-completions; passed on openai-responses.
  • Run 31843522932: failed on capi, anthropic-messages, openai-responses.

The test doesn't exercise the model provider at all, so the backend is not the variable — startup timing is.

Suggested fix

Widen the transient filter to cover both messages, e.g.:

transientExceptionFilter: ex =>
    ex.ToString().Contains("Extensions not available", StringComparison.OrdinalIgnoreCase)
    || ex.ToString().Contains("session not found", StringComparison.OrdinalIgnoreCase),

The same consideration applies to any other test that polls a session-scoped RPC immediately after CreateSessionAsync returns.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions