Session resume blocks on MCP OAuth tokens that cannot be refreshed, timing out after 60s instead of failing fast

Open
#3,640 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
48/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active

Research direction

No source file or test is named. Reproduce session resume with expired files lacking refreshToken under ~/.copilot/mcp-oauth-config, then trace the session resume and MCP reconnect path using the reported timeout logs. Done means unrenewable servers fail fast or are surfaced as needing sign-in without preventing the session from resuming.

Written by the indexing model from the issue text.

Description

App version

1.1.15 (Windows)

OS

Windows 11 Enterprise 10.0.26100 (build 26100), AMD64

Summary

When a cached MCP OAuth token has expired and has no refresh token, it
cannot be renewed without interactive sign-in. On session resume the app still
attempts a cached-token reconnect for each such server and waits, rather than
recognising the token as unrenewable and failing fast. With enough of these,
resume exhausts its 60s ceiling and fails.

Steps to reproduce
  1. Configure one or more MCP servers that authenticate via OAuth.
  2. Let their cached tokens expire without completing a re-auth prompt. Tokens
    accumulate in ~/.copilot/mcp-oauth-config/*.tokens.json; the ones that
    matter have a past expiresAt and no refreshToken field.
  3. Restart the app and resume an existing session.

Count the unrenewable tokens:

$now = Get-Date
Get-ChildItem "$env:USERPROFILE\.copilot\mcp-oauth-config" -Filter *.tokens.json |
  ForEach-Object {
      $j = Get-Content $_.FullName -Raw | ConvertFrom-Json
      if (-not $j.expiresAt) { return }
      # expiresAt is Unix seconds on Windows 1.1.15; handle ISO strings too.
      $exp = if ($j.expiresAt -is [string]) { [datetime]::Parse($j.expiresAt) }
             else { [DateTimeOffset]::FromUnixTimeSeconds([int64]$j.expiresAt).LocalDateTime }
      if ($exp -lt $now -and -not $j.refreshToken) { $_.Name }
  } | Measure-Object | Select-Object -ExpandProperty Count
Expected behavior

A token that is expired and has no refresh token is known-unrenewable before
any network call. Resume should skip it immediately (surfacing "needs sign-in"
for that server) and continue. One unreachable MCP server should not be able to
delay or fail session resume.

Actual behavior

The app attempts a cached-token reconnect per server and waits. Once enough of
them cannot answer, resume hits its ceiling and the session fails to open:

WARN  session::manager::lifecycle: session resume timed out; releasing CLI and surfacing error
WARN  session::manager::cli_pool:  force-retiring pooled CLI process
ERROR handlers::session:            extensibility RPC failed method=session.mcp.list error=request cancelled
ERROR handlers::session:            failed to resume session error=operation timed out:
                                    session resume timed out after 60s

I had 51 of 53 cached tokens expired, 45 of them with no refresh token.
Every resume of a previously working session failed at exactly 60s.

Impact — it cascades

The failure is not contained to one resume:

  1. Resume times out at 60s and the pooled CLI process is force-retired.
  2. The UI appears to hang. Windows logged AppHangB1 for github.exe 1.1.15
    twice within seven minutes, both under the same fault bucket
    (1215446218445078001) — so it is reproducible, not a one-off.
  3. The natural user response is to end the task in Task Manager, which kills
    only the root process and orphans its children (see companion issue about
    there being no way to fully quit the app).
  4. The next launch starts a fresh set of child processes on top of the strays.

After a few cycles I had ~23 app-related processes holding 4.3 GB, with
free RAM down from 12 GB to 7 GB. Every new session I opened was also affected,
so the app looked broadly broken rather than "one MCP server needs sign-in".

Fix that worked

Deleting only the expired token files resolved it completely:

before after
session resume timed out at 60s 6.6s – 8.2s
unreachable MCP server consumed the resume budget fails in < 1s
resume timeouts in log repeated 0

Same MCP servers, same config — only the dead tokens removed. That points at
the retry/wait path rather than the servers themselves.

Suggested fixes
  1. Fast-fail unrenewable tokens. If expiresAt is past and there is no
    refreshToken, skip the reconnect attempt entirely and mark the server as
    needing sign-in. This requires no network call to determine.
  2. Don't let MCP reconnect block resume. Resume the session first and let
    MCP servers attach asynchronously, or give MCP a budget well under the
    overall resume ceiling.
  3. Surface it in the UI. "3 MCP servers need sign-in" is actionable;
    a hang is not. Nothing in the UI indicated auth was the problem — I only
    found it by reading the logs.
  4. Prune or flag stale tokens. Tokens accumulated to 53 files over months
    with no visible signal. Even a warning at a threshold would have prevented
    this.
Workaround

Delete expired token files (valid ones are preserved):

$now = Get-Date
Get-ChildItem "$env:USERPROFILE\.copilot\mcp-oauth-config" -Filter *.tokens.json |
  Where-Object {
      $j = Get-Content $_.FullName -Raw | ConvertFrom-Json
      if (-not $j.expiresAt) { return $false }
      $exp = if ($j.expiresAt -is [string]) { [datetime]::Parse($j.expiresAt) }
             else { [DateTimeOffset]::FromUnixTimeSeconds([int64]$j.expiresAt).LocalDateTime }
      $exp -lt $now
  } | Remove-Item

Servers you actually use will prompt to re-authenticate on next launch.

Logs

Happy to attach /collect-debug-logs output if useful.

Dominant language
No language data
Stars
2.1k
Forks
157
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from github/app

All issues in github/app

Similar issues

More Security issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.