Skip to content

tmux *server* memory grows unbounded while sidebar runs (~2 GB/day) → slow new-window/split; not freed until kill-server #85

Open

Description

@trevnorris

Summary

When the sidebar runs for an extended period, the tmux server process (not the sidebar process) grows its memory monotonically and without bound — on my machine ~2 GB/day. Because every new-window/split-window makes the tmux server fork(), a bloated server makes new-pane creation extremely slow (sub-second → multi-second). The leaked memory is not reclaimed by closing the sidebar pane(s); only restarting the tmux server (kill-server) frees it.

I isolated this to the sidebar with an A/B test (below): an identical tmux server with the same driver but no sidebar stays flat.

Environment

  • tmux-agent-sidebar 0.10.1 (commit b56a47a)
  • tmux 3.2a
  • Pop!_OS 22.04 LTS, kernel Linux 7.0.9 x86_64
  • Usage: @sidebar_auto_create off, sidebar toggled manually, watching Claude Code / Codex agent panes
  • Affected server uptime when measured: ~5d 17h

Symptom

new-window / split-window take ~760 ms (well over 1 s once the login shell is layered on). A trivial tmux command (display-message) on the same server is ~4 ms, so the server's event loop is fine — the cost is in spawning a pane.

Root cause

The tmux server had RSS ≈ 2.1 GB + ~9.7 GB swap (~11.8 GB total) after ~6 days. But the actual live data is tiny:

total scrollback across all 11 panes  = 24.8 MB   (history-limit 10000)
paste buffers                         = 0

So ~11.8 GB is unaccounted-for heap. Creating a pane forks that giant address space (copying page tables, faulting swapped pages) → the ~760 ms. A fresh tmux server with my identical config forks a new window in 14 ms, so it is not the config or plugin load — it is accumulated server memory.

Reproduction (A/B isolation)

Isolated tmux server (-L), same driver (cycle window focus + nudge the sidebar to refresh), 3000 cycles, measuring the server process RSS:

start after 3000 cycles
with sidebar attached 4.0 MB 4.7 MB (monotonic)
control, no sidebar 3.9 MB 4.1 MB (flat)

Longer run (12000 cycles) shows clean linear growth: 4.0 → 5.1 → 6.2 → 7.4 MB.

Closing panes does not reclaim it

After the 12000-cycle run peaked at 7.4 MB:

PEAK with sidebar still open:      RSS = 7.4 MB
AFTER killing the sidebar pane:    RSS = 7.4 MB   (0 sidebar processes left)
AFTER closing all but one window:  RSS = 7.4 MB

RSS does not drop at all — the leak is in the persistent server process, so only tmux kill-server returns it to the OS.

Suspected area

The growth tracks the sidebar's refresh loop (per #46 it polls every ~2 s, and it capture-panes agent panes each cycle). Something in that per-refresh interaction with the tmux server accumulates server-side memory over time. Worth checking the capture-pane / poll path (src/tmux/query.rs, src/cli/capture/) and whether anything created server-side (buffers/options/hooks) isn't cleaned up — though I confirmed paste buffers were 0, so it may be a tmux-side allocation triggered by a specific repeated command.

Possibly related

Happy to run further diagnostics (I have a repro harness) or test a patch.

Activity

  1. trevnorris commented on Jun 10, 2026

    @trevnorris
    Author

    Confirmed still present on v0.12.0 (latest).

    Upgraded 0.10.1 → 0.12.0 and re-ran the same isolated A/B harness. (Note: the prebuilt tmux-agent-sidebar-linux-x86_64 asset requires GLIBC_2.39; on glibc 2.35 it won't run, so I built 0.12.0 from source with Rust 1.96.) The tmux server memory growth is unchanged:

    refresh cycles 0.10.1 server RSS 0.12.0 server RSS
    0 4.0 MB 3.9 MB
    4000 5.1 5.0
    8000 6.2 6.0
    12000 7.4 6.9

    Same ~0.25 MB / 1000-refresh monotonic growth; the no-sidebar control stays flat. So the leak is not fixed in v0.12.0.

    For reference, restarting the affected real server dropped it from 1,841 MB RSS (+ ~9.7 GB swap) → 3.9 MB, and new-window latency from ~760 ms → 6 ms, confirming the slowness was the bloated server's fork() cost.

  2. hiroppy commented on Jun 10, 2026

    @hiroppy
    Owner

    @trevnorris thank you for reporting and investigation! I didn't notice the memory issue. If you allow to fix this issue, I'm really happy but I don't want to spend your time if it has deep issue 🙇 anyway, let's understand your investigation

    my agent's memo

    Details
    I did some local investigation and I think the leak is probably not caused by `capture-pane`.
    
    The strongest suspect is the periodic refresh path:
    
    - `src/tmux/query.rs`
    - `query_sessions_with_process_snapshot()`
    - `tmux list-panes -a -F <pane_format>`
    - where `pane_format()` wraps every field with `#{q:...}`
    
    I reproduced the behavior in an isolated Ubuntu 22.04 container with tmux 3.2a. Repeatedly running the current sidebar-style refresh query caused the tmux **server** RSS to grow linearly:
    
    ```text
    tmux 3.2a
    
    == refresh_core ==
    start_rss_kb=3396
    4000 cycles rss_kb=11512
    8000 cycles rss_kb=19248
    12000 cycles rss_kb=27016
    end_rss_kb=27016
    
    
    
    
    But capture-pane alone stayed flat:
    
    text
    == capture_pane ==
    start_rss_kb=3396
    4000 cycles rss_kb=3500
    8000 cycles rss_kb=3500
    12000 cycles rss_kb=3500
    end_rss_kb=3500
    
    
    
    
    I also tested the focus-change hook style command (list-panes -t ... -F '#{@sidebar_pid}'), and that stayed essentially flat too.
    
    The key difference appears to be the #{q:...} format modifier. Splitting the format into smaller pieces showed this:
    
    text
    == builtin ==
    start_rss_kb=3392
    4000 cycles rss_kb=3792
    8000 cycles rss_kb=3792
    
    == builtin_q ==
    start_rss_kb=3392
    4000 cycles rss_kb=7116
    8000 cycles rss_kb=10632
    
    == user ==
    start_rss_kb=3436
    4000 cycles rss_kb=4288
    8000 cycles rss_kb=4288
    
    == user_q ==
    start_rss_kb=3384
    4000 cycles rss_kb=6796
    8000 cycles rss_kb=9456
    
    == full_no_q ==
    start_rss_kb=3392
    4000 cycles rss_kb=4192
    8000 cycles rss_kb=4192
    
    == full_q ==
    start_rss_kb=3392
    4000 cycles rss_kb=10620
    8000 cycles rss_kb=17512
    
    
    
    
    So the pattern seems to be:
    
    - list-panes itself: mostly flat
    - @pane_* user options: mostly flat
    - #{q:...}: linear tmux server RSS growth on tmux 3.2a
    - current full sidebar format with q: reproduces the leak signature
    
    On macOS with tmux 3.6b, I could not reproduce the linear growth; it rose slightly at startup and then flattened. So this may be a tmux 3.2a-era format modifier leak that the sidebar triggers frequently via its 1s refresh loop.
    
    A possible fix on the sidebar side is to remove #{q:...} from the hot refresh path and use a safer delimiter instead, for example ASCII Unit Separator (\x1f), then split on that. The hook-written fields like @pane_prompt / @pane_bg_cmd are already sanitized for newlines and pipes, so this should be manageable without relying on tmux’s q escaping in the high-frequency path.
    
  3. trevnorris commented on Jun 11, 2026

    @trevnorris
    Author

    Thanks for the info. I'll attempt to update my tmux locally and see if that resolves it.

  4. RayShark commented on Jun 18, 2026

    @RayShark

    PR #103: #103

    Fixes issue #85 by removing #{q:...} from the hot refresh path and sanitizing the pane values that round-trip through the raw query path.

    Verified the query-path tests and release build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions