Skip to content

~30 second MCP startup delay caused by feature flags fetch on high-latency networks (5s AbortController doesn't fire) #465

Description

@e-grn

Summary

On Windows 11 over a residential connection in Australia, every Desktop Commander cold start blocks for ~30 seconds between the client's initialize request and the server's initialize response. Claude Desktop reports the MCP server as failing to connect during this window; if the user is impatient and disables/re-enables the server, the cycle repeats.

The cause is the feature flags fetch in dist/utils/feature-flags.js:

this.flagUrl = process.env.DC_FLAG_URL ||
    'https://desktopcommander.app/flags/v2/production.json';
// ...
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const response = await fetch(this.flagUrl, { signal: controller.signal, ... });

The 5-second AbortController timeout does not prevent the 30s startup hang. The request seems to hang at the TCP connect level, which the abort signal apparently isn't interrupting on this Node/Windows combination, and ends up bound by the Windows-level TCP connect timeout instead.

The empirical proof is unambiguous: with WiFi disabled, startup completes in ~1.6 seconds (DNS resolution fails fast → fetch rejects immediately → process continues). With WiFi enabled, startup takes ~31 seconds every time.


Environment

Desktop Commander: 0.2.40 (installed as DXT extension)
Claude Desktop: 1.6608.0.0 (Microsoft Store / WindowsApps)
OS: Windows 11
Node.js: 24.15.0 (bundled with Claude Desktop)
undici: 7.24.4
Client: claude-ai 0.1.0
User location / network: Brisbane, Australia, residential ISP

Note: desktopcommander.app itself is reachable from this network — curl and a browser load it without trouble. The connect is just slow enough that the JS-level AbortController apparently doesn't preempt it.


Reproduction

  1. Install Desktop Commander on a Windows host located somewhere with high latency / slow TCP connect to desktopcommander.app (Australia, parts of Africa, anywhere with degraded transit to the flags origin).
  2. Restart Claude Desktop.
  3. Observe mcp-server-Desktop Commander.log — the time between the client's initialize request and the server's initialize response is ~30 seconds.

To confirm the cause:

  1. Disable the network adapter, restart Claude Desktop. Startup completes in ~2 seconds.
  2. Re-enable the network. Startup is slow again.
  3. As a workaround, blackhole the host in C:\Windows\System32\drivers\etc\hosts

0.0.0.0 desktopcommander.app

Startup returns to ~2 seconds permanently.


Evidence (log excerpts)

Slow startup (network enabled), ~31s gap:

05:23:01.443  Server started and connected successfully
05:23:01.498  Message from client: initialize  (id:0)
                  ⋮  31 seconds elapse  ⋮
05:23:32.599  Message from server: initialize response  (id:0)
05:23:32.601  Message from client: tools/list, prompts/list, resources/list
05:23:32.672  Replaying 2 buffered initialization messages
05:23:32.672  Loaded 3 feature flags from cache
05:23:32.673  MCP fully initialized, all startup messages sent
05:23:32.679  Message from server: tools list response  (id:1, ~78ms)

Fast startup (network disabled), ~1.6s gap:

05:50:23.380  Server started and connected successfully
05:50:23.385  Message from client: initialize  (id:0)
05:50:25.004  Message from server: initialize response  (id:0)
05:50:25.072  Loaded 3 feature flags from cache
05:50:25.073  MCP fully initialized, all startup messages sent
05:50:25.074  Failed to fetch feature flags: fetch failed   ← key line
05:50:25.079  tools list response  (~6ms)

The "Failed to fetch feature flags: fetch failed" line confirms the fetch is what's being preempted; without the network, it fails immediately and startup proceeds.


Root cause notes

featureFlagManager.initialize() is documented as non-blocking and the inner this.fetchFlags() call is fire-and-forget, so on paper this shouldn't delay the initialize response. In practice it does — the empirical 30s vs 1.6s difference correlates 1:1 with whether desktopcommander.app is reachable. Two possible (not mutually exclusive) explanations worth checking:

  1. AbortController not interrupting in-progress TCP connect in Node 24.15 / undici 7.24.4 on Windows for connections that are slowly succeeding rather than failing. The setTimeout(() => controller.abort(), 5000) fires, but the underlying socket continues to negotiate until the OS-level connect timeout returns (~30s on Windows by default), at which point the awaited fetch finally rejects.
  2. Some await chain blocking the SDK's initialize response on the fetch promise — e.g. via waitForFreshFlags() or an unawaited promise that nonetheless gates a downstream handler. (Couldn't pin this down by reading dist/, but it's consistent with the timing.)

Either way, the fix is to ensure the feature flags fetch can never delay startup, regardless of network conditions.


Suggested fixes

In rough priority order:

  1. Add an env flag to disable the fetch entirely, e.g. DC_DISABLE_FLAG_FETCH=1. Trivial to implement in feature-flags.js, immediately rescues affected users, and doesn't depend on diagnosing the AbortController behavior.
  2. Document DC_FLAG_URL in the README/FAQ so users with this problem can at least redirect the fetch to http://127.0.0.1:1 (which fails fast). Right now it's only discoverable by reading source.
  3. Hard-cap the fetch with Promise.race against an explicit setTimeout reject, so the 5s budget is enforced regardless of whether AbortController actually interrupts the underlying socket. Something like:
const fetchPromise = fetch(this.flagUrl, { signal: controller.signal, ... });
const timeoutPromise = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Feature flags fetch timed out')), 5000)
);
const response = await Promise.race([fetchPromise, timeoutPromise]);
  • Lower the fetch timeout to 2-3 seconds. The flags load from cache anyway; a fresh fetch isn't worth even 5 seconds of perceived startup latency.
  • Confirm fetchFlags() truly never blocks the initialize response. A unit/integration test that measures initialize latency with a mocked slow flags endpoint would catch regressions like this.

Workaround for affected users

Add to C:\Windows\System32\drivers\etc\hosts (run editor as Administrator):

0.0.0.0 desktopcommander.app

Followed by ipconfig /flushdns. Restart Claude Desktop. Startup drops from ~30s to ~2s. The flags subsystem keeps running off its local cache (Loaded 3 feature flags from cache), and as long as the cache file exists this has no functional impact.


Happy to provide additional logs or test patches against main if useful. Thanks for the project.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions