# Objective Fix memory a terminal never gives back after its command finishes. Each `terminal::Terminal` constructed a `vte::ansi::Processor`, and `Processor::new` reserves `SYNC_BUFFER_SIZE` (2 MiB) up front for synchronized updates. `Terminal::release_pty_resources` tore the PTY down once the command exited but left that reservation in place, so it lived exactly as long as the terminal did. That matters because the terminal outlives the command: the agent panel keeps each tool call's terminal so it can keep rendering the finished command's output (`ToolCallContent::Terminal(Entity<..>)`, `acp_thread.rs:2014`), and the panel's own terminal map is only cleared through `TerminalEvent::CloseTerminal`, which a task terminal never emits because `SpawnInTerminal::default()` sets `hide = HideStrategy::Never`. Measured on a release build running 50 agent commands (`ls`): peak heap 205.34 MB, 200.52 MB still retained at exit, and the heap grew in a staircase that never came back down (75 MB to 192 MB between 288s and 300s, then flat). heaptrack attributed the largest share, 104.00 MiB, to exactly 52 of these reservations (109,051,904 bytes = 52 x 2,097,152), all allocated from `TerminalBuilder::new` (`crates/terminal/src/terminal.rs:1309`) and never freed. So this costs roughly 2 MB per command for as long as its terminal is kept around. ## Solution - `output_processor` is only used by `Terminal::write_output`, which parses bytes *injected* into the terminal (init commands, terminal providers, the REPL). PTY output goes through the `alacritty_terminal` event loop's own parser, not this one. - Make the field an `Option`, create it on the first injected byte instead of at construction, and drop it in `release_pty_resources`, right after the PTY is torn down. - It is recreated if something injects bytes again, so `write_output` semantics do not change for terminal providers or init-command injection. As a side effect, terminals that never receive injected bytes no longer reserve the parser at all, even while they are live. ## Testing Added `tests::test_release_pty_resources_drops_an_allocated_parse_buffer` in `crates/terminal/src/terminal.rs`. It runs `echo` to completion in a task terminal the way an agent tool call does, and asserts the parse buffer's lifecycle directly on the terminal: - a terminal that received no injected bytes holds no parse buffer; - `write_output` builds one; - `release_pty_resources` drops it, while the output the terminal already captured is still readable; - injecting again after the release builds a new one. Asserting on the terminal's own state instead of on process-wide heap keeps PTY reader threads out of the measurement: they reserve parse buffers of their own and are torn down asynchronously, so a heap-based assertion would depend on a timing the test cannot synchronize with. Removing `self.output_processor = None` from `release_pty_resources` fails the test with `releasing the PTY should drop the parse buffer`. - `cargo test -p terminal`: 107 tests, all green. - `./script/clippy -p terminal`: clean. Tested on Linux. The fix and the test are platform-independent Rust (neither is behind a `cfg`, and Windows takes the same `TerminalType::Pty` branch), so I expect the same behaviour on macOS and Windows, but I have not verified it. ## 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 ## Suggested .rules additions Found while working on this, proposed rather than edited inline, per the rules-hygiene section. - `webrtc-sys`'s build script never reuses its prebuilt download: on every run it deletes the extracted package and re-downloads 131 MB from the GitHub release, and every distinct unit gets its own `OUT_DIR` and therefore its own copy (a debug build, a release build, and rust-analyzer's `cargo check --all-targets` are at least three). A build that appears to hang indefinitely at `webrtc-sys(build)` with no output is this. Avoid it by extracting the release asset once outside `target/` and setting `LK_CUSTOM_WEBRTC` to that directory through the user-level cargo config, so rust-analyzer's background checks inherit it too: ```toml # ~/.cargo/config.toml [env] LK_CUSTOM_WEBRTC = "/home/<user>/.local/share/libwebrtc/webrtc-0001d84-4/linux-x64-release" ``` `webrtc_sys_build::download_webrtc` returns early whenever that variable is set. If the path goes stale (the prebuilt is tagged, e.g. `webrtc-0001d84-4`), the build fails to link, so the variable has to be updated when the tag changes. --- Release Notes: - Fixed terminals retaining about 2 MB of memory per command after the command had finished
Zed
Welcome to Zed, a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.
Installation
On macOS, Linux, and Windows you can download Zed directly or install Zed via your local package manager (macOS/Linux/Windows).
Other platforms are not yet available:
- Web (tracking discussion)
Developing Zed
Contributing
See CONTRIBUTING.md for ways you can contribute to Zed.
Also... we're hiring! Check out our jobs page for open roles.
Licensing
Zed source code is licensed primarily under GPL-3.0-or-later, with Apache-2.0 components where marked.
License information for third party dependencies must be correctly provided for CI to pass.
We use cargo-about to automatically comply with open source licenses. If CI is failing, check the following:
- Is it showing a
no license specifiederror for a crate you've created? If so, addpublish = falseunder[package]in your crate's Cargo.toml. - Is the error
failed to satisfy license requirementsfor a dependency? If so, first determine what license the project has and whether this system is sufficient to comply with this license's requirements. If you're unsure, ask a lawyer. Once you've verified that this system is acceptable add the license's SPDX identifier to theacceptedarray inscript/licenses/zed-licenses.toml. - Is
cargo-aboutunable to find the license for a dependency? If so, add a clarification field at the end ofscript/licenses/zed-licenses.toml, as specified in the cargo-about book.
Sponsorship
Zed is developed by Zed Industries, Inc., a for-profit company.
If you’d like to financially support the project, you can do so via GitHub Sponsors. Sponsorships go directly to Zed Industries and are used as general company revenue. There are no perks or entitlements associated with sponsorship.