## Problem
GPUI's web dispatcher already uses `spin_send` and `spin_try_pop` on the
browser main thread. Those operations spin on `try_lock()` instead of
blocking while acquiring the queue mutex. However, that does **not**
make releasing a `parking_lot` mutex safe on that thread.
When a worker is waiting for the queue mutex, dropping the main thread's
guard follows this path:
```text
RawMutex::unlock_slow
→ parking_lot_core::unpark_one
→ lock_bucket
→ WordLock::lock_slow
→ ThreadParker::park
→ memory.atomic.wait32
```
`unpark_one` first locks the internal table used to locate sleeping
threads. If another thread holds that bucket lock, the caller can itself
park before reaching the actual wake instruction. See
[`unpark_one`](https://github.com/Amanieu/parking_lot/blob/d7828fff7b5d6327ae608e82db45f888b344449a/core/src/parking_lot.rs#L732-L737),
[`lock_bucket`](https://github.com/Amanieu/parking_lot/blob/d7828fff7b5d6327ae608e82db45f888b344449a/core/src/parking_lot.rs#L358-L366),
and [`WordLock`'s park
call](https://github.com/Amanieu/parking_lot/blob/d7828fff7b5d6327ae608e82db45f888b344449a/core/src/word_lock.rs#L163-L168).
Browsers forbid synchronous atomic waits on the main thread. Chromium
reports `Atomics.wait cannot be called in this context`; Safari can
misleadingly report an out-of-bounds memory access. Ordinary UI activity
can encounter this contention while scheduling background work; no
explicit blocking operation by the application is required.
## Why `std` fixes it
For **atomics-enabled Wasm**, Rust's standard-library mutex uses a futex
directly. Unlocking changes the mutex state with an atomic swap and, if
needed, calls `memory.atomic.notify`. Its condition-variable
notification similarly increments an atomic notification counter and
calls `memory.atomic.notify`. Neither operation acquires a secondary
waiter-table lock or performs an atomic wait.
A contended `std::Mutex::lock()` would still be unsafe on the browser
main thread, so this change **retains the existing `try_lock()` spin
loops**. Workers retain ordinary blocking acquisition and
condition-variable waiting. This relies on the Wasm backend's
implementation, not a portable guarantee that standard mutex operations
never block on every platform.
The production change only replaces the queue synchronization primitives
and adapts their APIs. The high/medium/low queues, weighted selection,
queue methods, and disconnect behavior are unchanged. Poisoned guards
are recovered to preserve the previous non-poisoning behavior. The
main-thread-only mailbox receiver also uses a standard mutex with
`try_lock()` so an unexpected acquisition cannot park it.
## Regression test and verification
One browser/Wasm stress test exchanges work and replies between the
actual browser main thread and configurable `N` Web Workers. It checks
exact-once delivery, cycles between idle and busy queues, and waits for
all workers to stop. An explicit Wasm wait is a negative control
confirming that the main thread rejects waits; a deadline outside the
page detects hangs.
- **Fix stashed:** rebuilt and ran the test against the original queue.
It failed with `Atomics.wait cannot be called in this context`, with
`WordLock::lock_slow → RawMutex::unlock_slow → spin_send` in the stack.
- **Fix restored:** rebuilt and ran the unchanged test with **8 and 16
workers**. Both passed, each exchanging **524,288 items over 128
rounds**.
- Both existing native queue tests passed. Shared-memory Wasm
compilation, Rust formatting, JavaScript syntax, and diff checks passed.
- Browser execution was verified in Chromium, not directly on an iOS
Safari device. This does not update any downstream application's pinned
GPUI dependency.
The test is available through the existing `hello_web` gallery:
```sh
node crates/gpui_web/examples/hello_web/test_queue.mjs http://127.0.0.1:8080 8
```
For the recorded runs, the fixture was built directly with the gallery's
shared-memory flags, `nightly-2026-07-01`, and matching wasm-bindgen
0.2.120, then served with COOP/COEP headers.
## Suggested .rules additions
For code running on the browser main thread, audit mutex unlock and
notification paths as well as acquisition: using `try_lock()` does not
guarantee that releasing the guard cannot park.
Release Notes:
- [GPUI] N/A
When a buffer is folded in the split diff view and then removed from the
multibuffer (via `remove_excerpts_for_path`), the block map's
`folded_buffers` set retains a stale entry for that buffer ID. Later,
when the split view syncs folded state from the RHS display map to the
LHS, it tries to look up the companion mapping for this stale buffer and
fails because the companion no longer tracks a buffer that's been
removed from the multibuffer.
This originally caused a panic via `.expect()`. This PR:
1. Converts the panic into graceful handling by skipping unmapped
buffers.
2. Actively removes stale entries from `folded_buffers` so they don't
persist as ghost state — ensuring that if the same buffer is later
re-added, it won't incorrectly appear as folded.
3. Adds a regression test covering the full lifecycle: fold → remove →
split → re-add → assert both LHS and RHS report the buffer as not
folded.
Release Notes:
- Fixed a crash in split diff view when a folded buffer was removed and
re-added.
The scheduler integration (#44810) removed the special handling for
realtime audio tasks that spawn them on a dedicated thread. This caused
a panic in the Mac dispatcher when RealtimeAudio priority was passed to
the regular dispatch path.
This restores the original behavior: RealtimeAudio tasks are spawned on
a dedicated thread via dispatcher.spawn_realtime(), using a bounded
channel to send runnables to it.
Release Notes:
- N/A
Fixes#29073
This PR reduces unnecessary GPU usage by being more selective about when
we present frames to prevent display underclocking (VRR optimization).
## Problem
Previously, we would keep presenting frames for 1 second after *any*
input event, regardless of whether it triggered a re-render. This caused
unnecessary GPU work when the user was idle or during low-frequency
interactions.
## Solution
1. **Only track input that triggers re-renders**: We now only record
input timestamps when the input actually causes the window to become
dirty, rather than on every input event.
2. **Rate-based activation**: The VRR optimization now only activates
when input arrives at a high rate (≥ 60fps over the last 100ms). This
means casual mouse movements or occasional keystrokes won't trigger
continuous frame presentation.
3. **Sustained optimization**: Once high-rate input is detected (e.g.,
during scrolling or dragging), we sustain frame presentation for 1
second to prevent display underclocking, even if input briefly pauses.
## Implementation
Added `InputRateTracker` which:
- Tracks input timestamps in a 100ms sliding window
- Activates when the window contains ≥ 6 events (60fps × 0.1s)
- Extends a `sustain_until` timestamp by 1 second each time high rate is
detected
Release Notes:
- Reduced GPU usage when idle by only presenting frames during bursts of
high-frequency input.
Fixes#42668
When clicking 'Configure' to enter a settings subpage, focus was being
lost because push_sub_page only called cx.notify() without managing
focus. Similarly, pop_sub_page had the same issue when navigating back.
This fix:
- Adds window parameter to push_sub_page and pop_sub_page
- Focuses the content area when entering/leaving subpages
- Resets scroll position when entering a subpage
Release Notes:
- Fixed a bug that prevented keyboard navigation in the settings window.
Closes#33018
### Problem
When opening a `PopoverMenu` or `RightClickMenu`, the pane's tab bar
buttons would flicker (disappear for a couple frames then reappear).
This happened because:
1. The menu is created and `window.focus()` was called immediately
2. However, menus are rendered using `deferred()`, so their focus
handles aren't connected in the dispatch tree until after the deferred
draw callback runs
3. When the pane checks `has_focus()`, it calls `contains_focused()`
which walks up the focus hierarchy — but the menu's focus handle isn't
linked yet
4. `has_focus()` returns false → tab bar buttons disappear
5. Next frame, the menu is rendered and linked → `has_focus()` returns
true → buttons reappear
### Solution
Delay the focus transfer by 2 frames using nested `on_next_frame()`
calls before focusing the menu.
**Why 2 frames instead of 1?**
The frame lifecycle in GPUI runs `next_frame_callbacks` BEFORE `draw()`:
```
on_request_frame:
1. Run next_frame_callbacks
2. window.draw() ← menu rendered here via deferred()
3. Present
```
So:
- **Frame 1**: First `on_next_frame` callback runs, queues second
callback. Then `draw()` renders the menu and connects its focus handle
to the dispatch tree.
- **Frame 2**: Second `on_next_frame` callback runs and focuses the
menu. Now the focus handle is connected (from Frame 1's draw), so
`contains_focused()` returns true.
With only 1 frame, the focus would happen BEFORE `draw()`, when the
menu's focus handle isn't connected yet.
This follows the same pattern established in 0b5f177399 which fixed the
identical issue for the editor's `MouseContextMenu`.
Fixes the hang introduced in #44995 (which was reverted in #45011) and
re-enables the optimization.
## Background
PR #44995 introduced an optimization to skip rendering lines that are
clipped by parent containers (e.g., when a large AutoHeight editor is
inside a scrollable List). This significantly improved performance for
large diffs in the Agent Panel.
However, #45011 reverted this change because it caused the main thread
to hang for 100+ seconds in certain scenarios, requiring a force quit to
recover.
## Root Cause
The original analysis in #45011 suggested that visible_bounds wasn’t
being intersected properly, but that was incorrect—the intersection via
with_content_mask works correctly. The actual bug: when an editor is
positioned above the visible viewport (e.g., scrolled past in a List),
the clipping calculation produces a start_row that exceeds max_row:
1. Editor’s bounds.origin.y becomes very negative (e.g., -10000px)
2. After intersection, visible_bounds.origin.y is at the viewport top
(e.g., 0)
3. clipped_top_in_lines = (0 - (-10000)) / line_height = huge number
4. start_row = huge number, but end_row is clamped to max_row
5. This creates an invalid range where start_row > end_row
This caused two different failures depending on build mode:
- Debug mode: Panic from subtraction overflow in
Range<DisplayRow>::len()
- Release mode: Integer wraparound causing blocks_in_range to enter an
infinite loop (the 100+ second hang)
## Fix
Simply clamp start_row to max_row, ensuring the row range is always
valid:
```rs
let start_row = cmp::min(
DisplayRow((scroll_position.y + clipped_top_in_lines).floor() as u32),
max_row,
);
```
## Testing
Added a regression test that draws an editor at y=-10000 to simulate an
editor that’s been scrolled past in a List. This would panic in debug
mode (and hang in release mode) before the fix.
Release Notes:
- Improved agent panel performance when rendering large diffs.
Fixes#44997
## Summary
Optimizes editor rendering when an editor is partially clipped by a
parent container (e.g., a `List`). The editor now only lays out and
renders lines that are actually visible within the viewport, rather than
all lines in the document.
## Problem
When an `AutoHeight` editor with thousands of lines is placed inside a
scrollable `List` (such as in the Agent Panel thread view), the editor
would lay out **all** lines during prepaint, even though only a small
portion was visible. Profiling showed that ~50% of frame time was spent
in `EditorElement::prepaint` → `LineWithInvisibles::from_chunks`,
processing thousands of invisible lines.
## Solution
Calculate the intersection of the editor's bounds with the current
content mask (which represents the visible viewport after all parent
clipping). Use this to determine:
1. `clipped_top_in_lines` - how many lines are clipped above the
viewport
2. `visible_height_in_lines` - how many lines are actually visible
Then adjust `start_row` and `end_row` to only include visible lines. The
parent container handles positioning, so `scroll_position` remains
unchanged for paint calculations.
## Example
For a 3000-line editor where only 50 lines are visible:
- **Before**: Lay out and render 3000 lines
- **After**: Lay out and render ~50 lines
## Testing
Verified the following scenarios work correctly:
- Editor fully visible (no clipping)
- Editor clipped from top
- Editor clipped from bottom
- Editor completely outside viewport (renders nothing)
- Fractional line clipping at boundaries
- Scrollable editors with internal scroll state inside a clipped
container
Release Notes:
- Improved agent panel performance when rendering large diffs.
This prevents the common footgun of copy/pasting an API key
starting/ending with extra newlines, which would lead to a "bad request"
error.
Closes#37038
Release Notes:
- agent: Support pasting language model API keys that contain newlines.
Release Notes:
- agent: Fixed a bug that canceled editing when scrolling the user
message out of view.
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
This was caused by calling `list_state.splice` on updated entries. We
don't need to splice the entry, as we'll recompute its measurements
automatically when we render it.
Release Notes:
- N/A
This pull request introduces title generation and history replaying. We
still need to wire up the rest of the history but this gets us very
close. I extracted a lot of this code from `agent2-history` because that
branch was starting to get long-lived and there were lots of changes
since we started.
Release Notes:
- N/A
This just prepares the types so that it will be easy later to update a
tool call with a terminal entity. We paused because we realized we want
to simplify how terminals are created in zed, and so that warrants a
dedicated pull request that can be reviewed in isolation.
Release Notes:
- N/A
---------
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
It's unfortunate to need to have access to a GPUI window in order to
create a terminal, because it forces to take a `Window` parameter in
entities that otherwise would have been pure models.
This pull request changes it so that we pass the `Project`'s entity id,
which is equally stable as the window id.
Release Notes:
- N/A
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This uses the `current_user` watch in the `UserStore` instead of looping
every 100ms in order to detect if the user had signed in.
We are changing this because we noticed it was causing the deterministic
executor in tests to never detect a "parking with nothing left to run"
situation.
This seems better in production as well, especially for users who never
sign in.
/cc @maxdeviant
Release Notes:
- N/A
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
In #35471, we added a new `AuthenticationError` variant to the client
enum `Status`, but the reconnection logic was ignoring it when
determining whether to reconnect.
This pull request fixes that regression and introduces test coverage for
this case.
Release Notes:
- N/A
This fixes a regression introduced in
https://github.com/zed-industries/zed/pull/35471, where we treated
stored credentials as invalid when failing to retrieve the authenticated
user for any reason. This had the side effect of triggering the auth
flow even when e.g. the client/server had temporary networking issues.
This pull request changes the logic to only trigger authentication when
getting a 401 from the server.
Release Notes:
- N/A
This pull request should be idempotent, but lays the groundwork for
avoiding to connect to collab in order to interact with AI features
provided by Zed.
Release Notes:
- N/A
---------
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
Previously, we would pick an exponent between 0.5 and 2.5, which would
cause a lot of clients to try reconnecting in rapid succession,
overwhelming the server as a result.
This pull request always doubles the previous delay and introduces a
jitter that can, at most, double it.
As part of this, we're also increasing the maximum reconnection delay
from 10s to 30s: this gives us more space to spread out the reconnection
requests.
Release Notes:
- N/A
---------
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
We believe this is causing the database to execute certain queries very
slowly, and it's unclear whether we're actually benefitting from it.
Release Notes:
- N/A
The `async-watch` crate doesn't seem to be maintained and we noticed
several panics coming from it, such as:
```
[bug] failed to observe change after notificaton.
zed::reliability::init_panic_hook::{{closure}}::hea8cdcb6299fad6b+154543526
std::panicking::rust_panic_with_hook::h33b18b24045abff4+127578547
std::panicking::begin_panic_handler::{{closure}}::hf8313cc2fd0126bc+127577770
std::sys::backtrace::__rust_end_short_backtrace::h57fe07c8aea5c98a+127571385
__rustc[95feac21a9532783]::rust_begin_unwind+127576909
core::panicking::panic_fmt::hd54fb667be51beea+9433328
core::option::expect_failed::h8456634a3dada3e4+9433291
assistant_tools::edit_agent::EditAgent::apply_edit_chunks::{{closure}}::habe2e1a32b267fd4+26921553
gpui::app::async_context::AsyncApp::spawn::{{closure}}::h12f5f25757f572ea+25923441
async_task::raw::RawTask<F,T,S,M>::run::h3cca0d402690ccba+25186815
<gpui::platform::linux::x11::client::X11Client as gpui::platform::linux::platform::LinuxClient>::run::h26264aefbcfbc14b+73961666
gpui::platform::linux::platform::<impl gpui::platform::Platform for P>::run::hb12dcd4abad715b5+73562509
gpui::app::Application::run::h0f936a5f855a3f9f+150676820
zed::main::ha17f9a25fe257d35+154788471
std::sys::backtrace::__rust_begin_short_backtrace::h1edd02429370b2bd+154624579
std::rt::lang_start::{{closure}}::h3d2e300f10059b0a+154264777
std::rt::lang_start_internal::h418648f91f5be3a1+127502049
main+154806636
__libc_start_main+46051972301573
_start+12358494
```
I didn't find an executor-agnostic watch crate that was well maintained
(we already tried postage and async-watch), so decided to implement it
our own version.
Release Notes:
- Fixed a panic that could sometimes occur when the agent performed
edits.
Release Notes:
- Improved the review experience in the agent panel. Now, when you
commit changes (generated by the AI agent) using Git, Zed will
automatically dismiss the agent’s review UI for those changes. This
means you won’t have to manually “keep” or approve changes twice—just
commit, and you’re done.
Previously, we forgot to associate the `Markdown` entity to
`MarkdownElement` during `prepaint`. This caused calls to
`Context<Markdown>::notify` to not invalidate the view cache, which
meant we would have to wait for some other invalidation before seeing
the results of that initial notify.
Release Notes:
- Improved responsiveness of mouse interactions with the agent panel.
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Fixes https://github.com/zed-industries/zed/issues/30346
The model can output an empty string to indicate the absence of
arguments, which can't be parsed as a `serde_json::Value`. When that
happens, we now create an empty object instead on behalf of the model.
Release Notes:
- Fixed a bug that prevented Copilot models from calling the
`diagnostic` tool.
This allows us to debug the raw edits that were generated when people
report feedback, when running evals and when opening the thread as
Markdown.
Release Notes:
- Improved debug output for agent threads.
Release Notes:
- Fixed a bug that would prevent the agent from working over SSH.
---------
Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Cole Miller <m@cole-miller.net>
Release Notes:
- Fixed a bug that would cause the message composer in the agent panel
to not render when the context strip was empty.
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Nathan Sobo <nathan@zed.dev>
The API will return a Bad Request (with no error message) when tools
were used previously in the conversation but no tools are provided as
part of a new request.
Inserting a dummy tool seems to circumvent this error.
Release Notes:
- Fixed an error that could sometimes occur when editing using Copilot
Chat.
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
This improves the new eval scenario by ~80% (`0.29` vs `0.525`) without
decreasing performance in the other evals.
Release Notes:
- Improved the performance of the `edit_file` tool.
Release Notes:
- Fixed a bug that would cause rejecting a hunk from the agent to delete
the file if the agent had decided to rewrite that file from scratch.
I also introduced a new eval to prove the encouragement actually makes a
difference.
Release Notes:
- Improved agent behavior when streaming edits, encouraging it to
editing files as opposed to creating them from scratch
Nathan here: I also tacked on a bunch of UI refinement.
Release Notes:
- Introduced the ability to follow the agent around as it reads and
edits files.
---------
Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Refs #29733
This pull request introduces a new field to the `StreamingEditFileTool`
that lets the model create or overwrite a file in a streaming way. When
one of the `assistant.stream_edits` setting / `agent-stream-edits`
feature flag is enabled, we are going to disable the `CreateFileTool` so
that the agent model can only use `StreamingEditFileTool` for file
creation.
Release Notes:
- N/A
---------
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
This pull request introduces a new tool for streaming edits. The
short-term goal is for this tool to replace the existing `EditFileTool`,
but we want to get this out the door as soon as possible so that we can
start testing it.
`StreamingEditFileTool` is mutually exclusive with `EditFileTool`. It
will be enabled by default for anyone who has the `agent-stream-edits`
feature flag, as well as people that set `assistant.stream_edits` to
`true` in their settings.
### Implementation
Streaming is achieved by requesting a completion while the `edit_file`
tool gets called. We invoke the model by taking the existing
conversation with the agent and appending a prompt specifically tailored
for editing. In that prompt, we ask the model to produce a stream of
`<old_text>`/`<new_text>` tags. As the model streams text in, we
incrementally parse it and start editing as soon as we can.
### Evals
Note that, as part of this pull request, I also defined some new evals
that I used to drive the behavior of the recursive LLM call. To run
them, use this command:
```bash
cargo test --package=assistant_tools --features eval -- eval_extract_handle_command_output
```
Or comment out the `#[cfg_attr(not(feature = "eval"), ignore)]` macro.
I recommend running them one at a time, because right now we don't
really have a way of orchestrating of all these evals. I think we should
invest into that effort once the new agent panel goes live.
Release Notes:
- N/A
---------
Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
Sometimes Gemini would report `Content` without a `parts` field.
Release Notes:
- Fixed a bug that would sometimes cause Gemini models to fail streaming
their response.
Release Notes:
- Improved performance of agent checkpoint creation.
- Fixed a bug that sometimes caused accidental deletions when restoring
to a previous agent checkpoint.
- Fixed a bug that caused checkpoints to be visible in the Git history.
This pull request will print all the used tools and their failure rates.
The objective goal should be to minimize that failure rate.
@tmickleydoyle: this also changes the telemetry event to report
`tool_metrics` as opposed to `tool_use_counts`. Ideally I'd love to be
able to plot failure rates by tool and hopefully see that percentage go
down. Can we do that with the data we're tracking with this pull
request?
Release Notes:
- N/A
Release Notes:
- Fixed a regression that caused the agent to hang sometimes.
---------
Co-authored-by: Thomas Mickley-Doyle <tmickleydoyle@gmail.com>
Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Michael Sloan <mgsloan@gmail.com>
Also, we moved the logic for driving the agentic loop into `Thread` so
that we don't have to re-implement it.
Release Notes:
- N/A
---------
Co-authored-by: Nathan Sobo <nathan@zed.dev>
This also increases the threshold for when we return an outline during
`read_file`.
Release Notes:
- Fixed an issue that caused the agent to fail reading large files if
the LSP hadn't started yet.
Release Notes:
- Fixed a rendering bug that caused context in the agent to not wrap
properly.
---------
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Zed AI <ai+claude-3.7@zed.dev>
It's super easy to undo those changes. In a future PR, we should also
avoid requiring confirmation in the batch tool if all the underlying
tools don't require confirmation.
Release Notes:
- N/A