Three coupled defects around the 60s init budget: non-configurable initializeTimeoutMs, misleading error text, and spawnConfigProbe launching a full CLI on a 500ms timer

Status Open
Reported on v2.1.251
Maintainer reply None cached
Activity 0 comments · opened Aug 29, 2026

Environment

  • Claude Code VSCode extension 2.1.251 (linux-x64), VS Code Insiders (remote/server)
  • Linux, 12 vCPU dev host
  • Code quoted below is from the shipped extension.js in the extension bundle

What's Wrong?

Host: 12 vCPU Linux dev box. Symptom: intermittent
Error: Subprocess initialization did not complete within 60000ms — check authentication and network connectivity

The three are reported together because they form a feedback loop: (3) creates load, load causes (1), and (2) sends you to debug the wrong subsystem.

---

1. initializeTimeoutMs is hardcoded and non-retrying

extension.js contains exactly one occurrence of the identifier — the destructuring default:

async function Wx0({ options: $, initializeTimeoutMs: Q = 60000 } = {}) { … }

No call site passes it and no setting reaches it, so 60s cannot be raised on a slow or loaded machine. For contrast, loadTimeoutMs appears 4× in the same file and is threaded through — so this looks like an omission, not a policy.

On timeout the handler rethrows; there is no retry or backoff:

await DX(H.initializationResult(), Q, B).catch((N) => {
  throw hD4(N, B) ? r5(N, { telemetryMessage: …, errorClass: "initialize_timeout" }) : N
});

Ask: expose initializeTimeoutMs as a setting, and/or retry once before surfacing.

2. The error text names two causes, neither of which is the common one

let B = `Subprocess initialization did not complete within ${Q}ms — check authentication and network connectivity`;

Our failures were CPU saturation (loadavg 44 on 12 vCPU), with auth and network both healthy. The message sends you to the two subsystems that were fine. Notably the telemetry path already classifies this precisely as errorClass:"initialize_timeout" — the user-facing string is simply less accurate than the internal one.

Ask: drop the speculative causes, or add the actual elapsed/load context.

3. spawnConfigProbe launches a full Claude process to read config, on a 500ms fallback timer

loadConfig() {
  … $.fallbackTimer = setTimeout((Y) => {
        if (this.configResolver !== Y) return;
        this.configResolver = void 0;
        this.spawnConfigProbe().then(Y.resolve, Y.reject);
      }, 500, $)
}

async spawnConfigProbe() {
  this.logger.log("Loading config cache by launching Claude (no channel)...");
  let J = await this.spawnClaude(Q, void 0, async () => ({ behavior: "deny", message: "Config loading only" }), …),
      X = await J.initializationResult();     // ← subject to the same 60s budget
}

This is the loop:

config cache misses its 500ms window → spawn a full claude → that process pays the entire init cost and raises load → the next config load is slower → more probes.

A 500ms threshold is easy to blow on a busy machine, so the mitigation fires exactly when it is most expensive.

Observed, on the host: four claude processes initialized within 51 seconds; loadavg went 0.48 → 6.87 across them. Three left no transcript and are gone — they ran the full startup path (including every SessionStart hook) and never held a conversation. The fourth, a real session, was the slow one.

We measure init cost with a SessionStart hook publishing process age as a lower bound. Across those four:

| loadavg (1m) | init elapsed (lower bound) |
|---|---|
| 0.48 | 2.5 – 3.5 s |
| 2.29 | 1.9 – 2.9 s |
| 6.45 | 3.9 – 4.9 s |
| 6.87 | 7.4 – 8.4 s |

(Ranges, not points: our first version of this hook derived elapsed from
EPOCHSECONDS and /proc/stat's btime, both floored to whole seconds, which
left up to 1s of bias. We have since switched to boot-relative time. The spread
does not affect the trend, which is what matters here.)

We cannot prove those three specific PIDs were probes — we did not capture their argv. But the mechanism is present in shipped code, it fires on a 500ms timer, and it produces exactly this signature.

Ask: read config without spawning a full CLI; failing that, raise/back off the 500ms timer, make probes skip user hooks, and mark them so they are attributable.

---

Why the three compound

An extension-side mitigation (3) generates the load that trips a non-configurable budget (1), and the resulting message (2) points at auth and network. Each is minor alone; together they produce a recurring failure that is actively misleading to debug.

View original on GitHub ↗