[BUG] VS Code extension: PreToolUse callback saveFileIfNeeded never resolves — every Read/Write waits 600 s and fails ("host client may be unreachable")
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet (closest: #94887 — same error text, different setup)
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code (2.1.281)
What's Wrong?
In a long-running VS Code session, every Read and Write tool call started to fail after exactly 600 s:
PreToolUse hook did not respond before its timeout (host client may be unreachable). The tool call was not executed; other configured hooks may not have completed.
8 of 8 file-tool calls after the first failure (7× Read, 1× Write, different files, over ~2 h) hung for 600 s each and were not executed. Bash kept working normally in the same period (102 calls dispatched without problems). So each file-tool call costs 10 minutes and returns nothing; the agent can only work through Bash.
It is not a user hook. The only settings-level PreToolUse hook matching Read is a command hook with timeout: 10; its output is logged ~0.4 s after the call. The wait is the extension's own SDK hook callback. From extension.js (2.1.281):
hooks: {
PreToolUse: [
{ matcher: "Edit|Write|MultiEdit", hooks: [(T) => q.captureBaseline(T)] },
{ matcher: "Edit|Write|Read", hooks: [(T) => this.saveFileIfNeeded(T)] }
],
PostToolUse: [{ matcher: "Edit|Write|MultiEdit", hooks: [(T) => q.findDiagnosticsProblems(T)] }]
}
No timeout is set, so the CLI default (600 s) applies. saveFileIfNeeded awaits vscode.workspace.openTextDocument(uri) and, for a dirty document, save() — also with no timeout. For the hung Write, the other callback (captureBaseline) did run: [DiagnosticTracking] Captured baseline diagnostics for … is logged in the same second. So the CLI ↔ extension callback channel works, and saveFileIfNeeded is the call that never resolves.
Workaround attempt: adding "claudeCode.autosave": false to user settings (this makes saveFileIfNeeded return on its first line) did not help in the already-stuck window. A probe Read 25 s after the change hung for 600 s again, so the stuck extension host apparently never picked up the configuration change. Reload Window has not been tried yet.
What Should Happen?
- SDK hook callbacks registered by the extension should have a short timeout and fail open (
{continue: true}).saveFileIfNeededis a convenience; it should never block a tool call for 10 minutes. openTextDocument/save()inside it should be raced against a timeout of a few seconds.- The error should name the pending callback (e.g.
saveFileIfNeeded) instead of "host client may be unreachable", and the extension log should record callback start/end. Currently nothing in the extension log shows which callback is pending.
Error Messages/Logs
Claude VSCode.log (CLI debug output, UTC), first hung Read:
08:13:20.627Z [DEBUG] Hook output does not start with {, treating as plain text <- user command hook finished
08:23:20.605Z [ERROR] hook failed: core: errorKind=AbortError errorChars=0 (classic.PreToolUse; skipped; what is below it ran in its place)
08:23:20.605Z [DEBUG] managed: its next() rejected below it (classic.PreToolUse); the rejection passes up
08:23:20.605Z [ERROR] hooks chain failed: errorKind=AbortError errorChars=0
08:23:20.607Z [DEBUG] PreToolUse hook timed out (per-hook abort)
08:23:20.630Z [DEBUG] hooks module agents-md@builtin tool.call settled in 600066.8ms (native link, next() included)
Last successful Read: 07:57:28Z ([Stall] tool_dispatch_end tool=Read … outcome=ok durationMs=2).
Possible trigger (time correlation only), from VS Code main.log and the second window's renderer.log:
08:12:29Z a second VS Code window opened ("Started local extension host with pid …")
08:12:43Z [warning] No ptyHost heartbeat after 6 seconds
08:12:51Z Extension host with pid … exited with code: 0 (the second window's)
08:12:51Z [error] vscode-file: Failed to load resource ~/.vscode/extensions/anthropic.claude-code-2.1.220-darwin-arm64/resources/claude-logo.svg net::ERR_FILE_NOT_FOUND
08:13:20Z first hung Read in the first window
The extension had auto-updated from 2.1.220 to 2.1.281 earlier that morning.
Steps to Reproduce
Not reproduced deliberately. Observed state: a long session (~7 h) in a VS Code window with two Claude Code tabs, Remote Control connected, auto mode; a second VS Code window was opened. After that, every Read/Write in the first window's session hangs for 600 s. Edit goes through the same callback but was not attempted during the hang.
Claude Model
Opus (claude-opus-5-5, 1M context)
Is this a regression?
I don't know
Claude Code Version
2.1.281 (VS Code extension, darwin-arm64)
Platform
Anthropic API (first party)
Operating System
macOS 26.6.2
Terminal/Shell
VS Code 1.139.0 (extension), zsh
3 Comments
Same failure in the Claude desktop app (Code tab) on Windows 11 Pro (build 26200), so it does not look specific to the VS Code extension.
From the session transcripts (
~/.claude/projects/**/*.jsonl), measuring the time fromtool_useto the error result:| Time (UTC) | Tool | Waited | Version |
|---|---|---|---|
| 2026-09-25 16:07 | Edit | 600.0 s | 2.1.280 |
| 2026-09-25 16:18 | Edit | 600.0 s | 2.1.280 |
| 2026-09-25 17:42 | Write | 600.0 s | 2.1.280 |
| 2026-09-26 04:12 | Read | 600.0 s | 2.1.280 |
| 2026-09-26 04:25 | Read | 600.0 s | 2.1.280 |
| 2026-09-26 04:38 | Write | 600.0 s | 2.1.280 |
Every one returned
PreToolUse hook did not respond before its timeout (host client may be unreachable). The tool call was not executed.PreToolUsehooks exist in any settings file (only Stop, SessionEnd, Notification and SessionStart), so the hook that timed out is the host's own.Edit|Write|Readmatcher described above.Workaround: after the first 600 s failure, stop retrying file tools in that session (each retry costs another 10 minutes). Either do file work through the shell tool or start a new session.
A timeout of a few seconds on the host's file-save callback, falling through to "not saved" rather than blocking, would turn a 10-minute stall into a non-event.
Still reproducing on releases that carry the 2.1.285 fix: macOS 26.5.2, VS Code 1.140.0, extension 2.1.285 then 2.1.289; Windows, Claude Code 2.1.286 (entrypoint not recorded). Changelog 2.1.285: "[VSCode] Fixed every file Read, Write and Edit stalling for ten minutes and then being skipped when the editor stops responding to the extension's automatic save before the tool runs."
Counts, from session transcripts
| Machine, window (EDT = UTC-4) | Version | Timed-out calls | Waited |
|---|---|---|---|
| macOS, 10-04 18:37 to 10-05 07:29 | 2.1.285 | 104, in 7 sessions | 18.7 h |
| Windows, 10-02 to 10-04 (UTC dates) | 2.1.286 | 120, in 48 transcripts | 27.4 h |
| macOS, 10-05 15:06 and 16:39 | 2.1.289 | 4 | 0.7 h |
On macOS 102 of 108 timed-out calls waited 600 s to within a second; six waited longer, up to 2,383 s; 95 were in subagents.
A window reload at 07:57 EDT, which also moved the extension from 2.1.285 to 2.1.289 (not separable), was followed by about seven hours with no timeout: 135 calls completed, two Writes took 433 s and 370 s. Then it returned on 2.1.289: the four timeouts above, plus two Reads that took 339 s and 469 s. During the 16:39 pair of timeouts (a sample at 16:48 EDT) the host read about 100 percent CPU and a 5.2 GB footprint.
New: a join with VS Code's own record (macOS only)
The renderer log writes
Extension host (LocalProcess pid: N) is unresponsive.and later... is responsive.. I joined the 1,475 Read/Write/Edit calls started from 10-04 12:00 to 10-05 18:22 EDT with the 1,521 closed intervals in that span:Caveats: intervals average one per 72 s and cover a quarter of the span, clustered by hour, so a 600 s call touches one largely by chance; the completed-call lines are the sharper test. No closed interval exceeds 600 s (longest 398 s); one interval that began at 16:54 EDT, after the last timeout, had no matching 'responsive' line before the host was replaced 2,055 s later. Timed-out calls overlapped a median 80 percent of their wait, the four 2.1.289 timeouts only 0.3 and 1 percent. So a 600 s wait is not one long logged host stall. Correlation, not cause.
Why the host stalls is unknown: VS Code's profiler named an extension in 42 of 1,539 profiles (about 70 s, never Claude Code); 778 ended with no name and no cancel. In the host started at 17:29 EDT, six file-tool calls 50 minutes later took 0.03 to 1.01 s; from 18:27 the extension's own log fell behind the sessions' timestamps by a maximum of 18 s in the 18:27 minute, 43 s at 18:28 and 118 s at 18:30, with no file-tool call in that span.
The 10-second guard in 2.1.289
The extension log warns
The editor has not opened <path> after 10 seconds, so the Read tool goes ahead with the file as it is on disk. .... For the two slow Reads that warning was logged within 0.05 s of each Read returning (15:20:17 and 15:49:14 EDT), 339 s and 469 s in, not at 10 s.PreToolUse hook timed out (per-hook abort)is logged 74 times on the 10-04 evening and four times on 2.1.289.In 2.1.289's
extension.js(grepdeadlineMs),saveFileIfNeededracesopenTextDocumentagainst 10 s andsave()against 120 s, both timed bysetTimeoutinside the extension host. The hook is registered as(k,f,{signal:p})=>this.saveFileIfNeeded(k,p), with no timeout field. I read only 2.1.289, not 2.1.285 or the CLI.What I tried and saw
One reload, above: confounded with the version change, then slow Writes, then a relapse. Seven file-tool calls of processes that predated it were refused, so stop in-flight work first.
Ask
The warning above was logged seven times on 2.1.289 after the reload; for the two slow Reads it came within 0.05 s of the Read returning, 339 s and 469 s in. So the 10-second bound inside the extension host did not bound those waits; and the four 2.1.289 timeouts overlapped a logged unresponsive interval for only 1.6 s and 6.2 s of their 600 s, so a logged host stall does not account for them either. Why a timer set for 10 s fired at 339 s is not shown by these logs. Ask: time out extension-registered hook callbacks on the CLI side after a few seconds and fail open so the tool still runs; name the pending callback in the error; and when the editor step cannot complete, read the file from disk as the 10-second path already does, rather than dropping the call.
Setup: a large multi-repository workspace with git worktrees under one root (the language server listed 840,000 to 940,000 files); five to ten panel sessions in one VS Code window, often with parallel subagents, over several days. Since 19:05 EDT today
claudeCode.autosaveis off on macOS, with a language-server exclusion (not separable); no result yet. On Windows the extension host also ran out of heap 14 times on 10-05, a different symptom.Addendum to my comment above: what was pinning the extension host is now identified, and it was not Claude Code. Three loads on the host's single main thread, measured the same evening on the macOS box, each confirmed by removing it:
workspaceContains:**/*.py(vscjava.migrate-java-to-azure): VS Code's own profiler named it in 22 of 28 attributed unresponsive windows, with the host at about 110 % CPU and 4.7 GB while no Claude Code session was running under it. Uninstalled; the next host start idled at 0.1-0.9 % CPU.sampleof the pinned host put 1,697 of 2,186 main-thread samples in synchronousnode:sqliteStatementSync::Runon a 445 MBcodebase-external.sqlitethat was being rewritten every few seconds. Turninggithub.copilot.chat.workspace.codeSearchExternalIngest.enabledoff only takes effect at the next host start.git.enabledoff for the workspace: 0 git commands, 0 repositories opened, 0 unresponsive lines.This does not change the ask, it sharpens it: the host gets blocked by ordinary third-party work in a large workspace, and a Claude Code file tool then waits the full 600 s hook timeout and loses the call. A few-second CLI-side timeout on extension-registered hook callbacks, failing open to the on-disk read the 10-second path already does, would make that a non-event. A corollary for anyone hitting this: a host pinned with no Claude session under it is the quick proof the load is not Claude Code's, and
exthost/<extension>/*.lognames the culprit faster than the profiler, which only runs if the stall lasts 5 s.