Windows: main thread hangs for minutes when closing the agent diff view after a long agent editing session
area:ai/agent threadreach:some usersseverity:S2
During a long agent editing round on a large repository, Zed's main thread can enter one giant paint pass and stop pumping its task queue for **minutes at a time**. The window keeps rendering and the mouse wheel still scrolls (composition surface moves), but clicks and keyboard input are ignored, Windows plays the "busy" ding on clicks, and one CPU core is pegged at 100%. The process is busy, not deadlocked — it's stuck converting a huge edit backlog through uncached sum-tree cursors while rebuilding the agent diff multibuffer's display map.
This has happened to me repeatedly for months, always in the same scenario: **agent panel + large repo + long editing round + agent diff view open + closing that view (or another unsaved buffer) mid-stream**.
### Reproduction steps
Not 100% deterministic (needs a large edit backlog to accumulate), but it triggers reliably for me once per long session:
1. Start Zed on Windows (native, not WSL). Use a recent nightly build.
2. Open a **large** repository — thousands of files. Mine was a big Java codebase (Minecraft decompiled sources) with the **JDT LS** Java language server active on it. Any large repo should do; the bigger the file count and the more edits, the worse it gets.
3. Open the **Agent Panel** and start a long agent session whose instructions make the agent perform **many small edits across many files over many tool-call rounds**. My trigger session was "Java Package Declaration Mismatch Fix", e.g.:
> Go through the codebase and fix all package-declaration mismatches, editing each file yourself.
4. Keep the **agent diff view** (the multibuffer preview of the agent's edits) open while the agent streams. Let it accumulate hundreds of edits (my trigger session ran ~3.5 h with a long storm of tool-call chunks near the end; process uptime at the time was ~8 h).
5. While the agent is still streaming (or right after a burst), click the **close button on the agent diff multibuffer** — closing another *unsaved* edited buffer around the same time also seems to trigger it.
6. **Observed result:** the window freezes — see below. (I killed the process after several minutes; I have not waited one out, so I can't say whether it ever recovers.)
You can watch it build up: Zed's own hang detection fires increasingly long foreground tasks as the session progresses (see the log excerpt — `write_file_tool` then a 464 ms paint task at `window.rs:713`), and resident memory jumps ~1.8 GiB in the final 7 minutes before the freeze.
How to tell you've triggered it: one core sits at 100% in Task Manager, clicking the window plays the Windows "busy" sound, the mouse wheel still scrolls the surface, the window keeps painting normally, but no clicks/keys take effect, and `zed.log` stops making progress.
### Zed version and system specs
```
Zed Dev 1.17.0
Commit: 7da0091d214477a314b72d8113d2ebac6a26d0a1
Version: 1.17.0+dev.7da0091d214477a314b72d8113d2ebac6a26d0a1
Built locally from a mirror of zed main: upstream content through
"Adjust language docs (#62551)" (upstream SHA 7733b992, 2026-08-13) plus my
still-unmerged PR #62191 ("agent: Add tools allowlist to spawn_agent tool",
2 local commits, +545/-7 in 9 files). The PR only touches crates/agent*,
crates/agent_ui and crates/zed — none of the editor/gpui render path cited
below — so this should reproduce on plain main.
OS: Windows 11 Pro (10.0.26200)
Memory: 32 GiB
Architecture: x86_64
CPU: 20 logical cores
GPU: NVIDIA GeForce RTX 3080
```
Observed across many nightlies over the last few months, not specific to this one build.
### Attach Zed log file
<details><summary>Zed.log (excerpt, from the hung session's Zed.log.old)</summary>
```log
2026-08-17T00:04:49+08:00 INFO [agent] Received prompt request for session: 1167105e-0868-4f3d-aa86-8ad450704261
2026-08-17T00:04:49+08:00 INFO [agent::thread] Thread::send called with model: k3
...
2026-08-17T01:40:39+08:00 INFO [zed::reliability::hang_detection::logging] New foreground hang detected:
Tasks(s) that ran too long
507.6637ms - crates\worktree\src\worktree.rs:1393:37
2026-08-17T03:27:28+08:00 INFO [zed::reliability::hang_detection::logging] New foreground hang detected:
Tasks(s) that ran too long
307.7941ms - crates\agent\src\tools\write_file_tool.rs:242:12
2026-08-17T03:30:57+08:00 INFO [zed::reliability::hang_detection::logging] New foreground hang detected:
Tasks(s) that ran too long
464.2172ms - crates\gpui_windows\src\window.rs:713:14
2026-08-17T03:39:21+08:00 INFO [zed::reliability] memory usage: resident 2365 MiB (+16 MiB), virtual 3319 MiB
2026-08-17T03:46:27+08:00 INFO [zed::reliability] memory usage: resident 4168 MiB (+1802 MiB), virtual 3311 MiB
<-- log ends here: the main thread froze around 03:46:27; dumps were taken 03:45:58–03:47:51 -->
```
The log also spams `WARN [project::lsp_store] skipping diagnostics update, no worktree found for path "C:\Users\<user>\AppData\Local\Temp\jdtls_decompiled\..."` during the storm (JDT LS diagnostics racing the edits). The session log contains 30 `New foreground hang detected` events over the preceding days of uptime — the problem builds up gradually before the final freeze.
Full log (1 MB, 3 days of uptime) available on request.
</details>
### (AI issues) Model provider details
```
- Provider: Kimi (Moonshot) via a custom openai_compatible provider
(api_url https://api.kimi.com/coding/v1)
- Model Name: k3 (k3-256k also configured)
- Mode: Agent Panel (the diff view of the agent's edits was open during streaming)
- Other details: two MCP context servers active (github, bind-alias); the agent
made heavy use of write_file/edit_file tool calls; JDT LS running on the Java
worktree. My unmerged PR #62191 adds a tools allowlist to spawn_agent but does
not touch the render path.
```
### Current vs. Expected behavior
**Current:** closing the agent diff view (or an unsaved buffer) mid-session sends the main thread into one enormous paint pass. The UI stops consuming input for minutes; Windows treats the window as busy; the agent's stream stalls (its completion needs the main thread). One core pegged the whole time.
**Expected:** closing a view returns instantly (or at worst blocks briefly), the agent keeps streaming, and the UI stays interactive.
### Diagnosis / evidence
Collected from **6 full memory dumps** (`zed.exe_2026-08-17_03-{45-58,46-35,46-54,47-05,47-18,47-51}.dmp`, ~4.4 GB each) taken over 113 seconds while hung. All symbolization done against the exact build's PDB.
1. **The main-task queue was frozen.** The gpui `PriorityQueueState<RunnableVariant>` (`crates/gpui/src/queue.rs:26`) held an **identical backlog of 1301 medium-priority runnables in all six dumps** (head/len/cap/buffer/receiver_count byte-identical across 113 s). `run_foreground_task` (`crates/gpui_windows/src/platform.rs:999-1060`) pops exactly one runnable per pump iteration, so *any* return to the pump would have shrunk the backlog: the main thread never reached the task pump once during the whole window.
2. **The thread was executing, not deadlocked** — each dump catches a different render-phase frame (dump 1: `create_highlight_endpoints`; dump 2: `SumTree::find`; dump 4: `InlaySnapshot::to_offset`; dump 5: taffy `compute_child_layout`; dump 6: `seek_internal` under display-map sync), and dump 6's outermost frames are one `WndProc` → `end_lease<DisplayMap>` invocation: **one continuous WM_PAINT pass**.
3. **Hot chain** (symbolized, dump 6):
```
gpui_windows::window::window_procedure
→ gpui::app::entity_map::EntityMap::end_lease<DisplayMap>
→ DisplayMap::snapshot → TabMap::sync / FoldMap::sync
→ FoldPoint::to_offset → InlaySnapshot::to_offset (calls buffer.point_to_offset twice)
→ MultiBufferSnapshot::convert_dimension (fresh MultiBufferCursor per call)
→ MultiBufferCursor::region() (per-cursor OnceCell) → rope Cursor::seek_internal
```
4. **Why it's so expensive:** each conversion walks the sum trees from scratch — `convert_dimension` creates a fresh cursor per call (`crates/multi_buffer/src/multi_buffer.rs:4329-4341`), `region()`'s `OnceCell` is per-cursor so nothing is reused across calls (`multi_buffer.rs:7064-7067`), and `InlaySnapshot::to_offset` calls `point_to_offset` twice (`crates/editor/src/display_map/inlay_map.rs:889-908`). `TabMap::sync` loops over every fold edit doing these conversions, and `create_highlight_endpoints` (`crates/editor/src/display_map/custom_highlights.rs:73-110`) rebuilds endpoints per highlight tag in the render. Net effect: O(E · log n) with a large constant, zero cross-call caching — one pass over the session's whole edit backlog takes minutes.
5. **Backlog composition:** the frozen queue decodes mostly to dropped-task destructors (`state = SCHEDULED|CLOSED|REFERENCE`) from spawn-and-replace debounce patterns at `crates/editor/src/scroll.rs:445` (`hide_scrollbar_task`) and `crates/editor/src/editor.rs:11051` (`post_scroll_update`) — two corpse tasks minted per scroll/update event, plus the app's live work (agent events, LSP, sidebar, action log) parked behind the one monster pass. *(Sampled, not fully enumerated.)*
6. **Aggravator:** every tool-call chunk and edit event triggers two full rebuilds of the agent diff multibuffer — `AgentDiffPane::update_excerpts` (`crates/agent_ui/src/agent_diff.rs:113-131`) and `AgentDiff::update_reviewing_editors` (action-log observer at `1335-1340` plus `EntryUpdated` at `1468-1477`) — each dirtying the window and feeding the display-map sync + full render of a megabyte-scale multibuffer.
Why it looks Windows-specific: the 10 ms budget in `run_foreground_task` only applies between runnables; a single `WM_PAINT` dispatched inside `WndProc` blocks the whole pump for as long as the paint takes. On other platforms the same heavy pass presumably manifests as a long freeze inside a frame instead.
I can share the dumps/PDB-matched stack extracts privately on request (too large for the issue).
### Suggested fix directions (for maintainers)
1. **Cache cursors/endpoints across the monotonic edit sequence** in `TabMap::sync`/`FoldMap::sync` (reuse the `MultiBufferCursor` and `region()` across consecutive conversions instead of a fresh cursor per call) and cache highlight endpoints per range in `create_highlight_endpoints`. This removes the per-edit constant and is the real fix for the close-time pass.
2. **Debounce the agent-diff rebuilds**: coalesce `update_excerpts` / `update_reviewing_editors` bursts (200 ms) so a storm of chunks/`EntryUpdated` events produces one rebuild instead of two full rebuilds per event. I have a prototype patch that does this (reduces churn but does not fix the per-pass cost itself).
3. Consider whether a very long `WM_PAINT` should be able to yield back to the task pump on Windows (the current pump can't preempt a single WndProc dispatch).
### Relevant settings / keymap
<details><summary>settings.json (masked excerpt — API tokens and user paths redacted)</summary>
```json
{
"language_models": {
"openai_compatible": {
"Kimi": {
"api_url": "https://api.kimi.com/coding/v1",
"available_models": [
{ "name": "k3-256k", "max_tokens": 256000, "max_output_tokens": 80000, "max_completion_tokens": 80000, "reasoning_effort": "high", "capabilities": { "tools": true, "images": true, "parallel_tool_calls": true, "prompt_cache_key": true, "chat_completions": true, "interleaved_reasoning": true, "max_tokens_parameter": false } },
{ "name": "k3", "max_tokens": 1000000, "max_output_tokens": 384000, "max_completion_tokens": 384000, "reasoning_effort": "high", "capabilities": { "tools": true, "images": true, "parallel_tool_calls": true, "prompt_cache_key": true, "chat_completions": true, "interleaved_reasoning": true, "max_tokens_parameter": false } }
]
}
}
},
"context_servers": {
"mcp-server-github": { "enabled": true, "remote": false, "settings": { "github_personal_access_token": "ghp_REDACTED" } },
"bind-alias": { "enabled": true, "remote": false, "command": "F:\\workspace\\bind-alias\\src\\client\\resources\\mcp\\proxy.cmd" }
},
"agent": {
"show_turn_stats": true,
"model_parameters": [
{ "provider": "Kimi", "model": "k3", "temperature": 1 },
{ "provider": "Kimi", "model": "k3-256k", "temperature": 1 }
],
"sandbox_permissions": { "allow_unsandboxed": true },
"auto_compact": { "threshold": "74%" },
"tool_permissions": { "tools": { "terminal": { "default": "allow" }, "write_file": { "always_allow": [ { "pattern": "^C:/Users/<user>/\\.agents/skills/" }, { "pattern": "^C:/Users/<user>/\\.agents/demo/" } ] }, "delete_path": { "default": "allow" }, "edit_file": { "always_allow": [ { "pattern": "^C:/Users/<user>/\\.agents/skills/" }, { "pattern": "^C:/Users/<user>/\\.agents/demo/" } ] }, "skill": { "default": "allow" }, "copy_path": { "default": "allow" }, "create_directory": { "default": "allow" }, "move_path": { "always_allow": [ { "pattern": "^F:/workspace/BindAlias/" } ] }, "fetch": { "default": "confirm" } } }
}
}
```
</details>
0 条评论