Memory, zRAM and swap on Linux with agents in the terminal
Updated at October 7, 2026
Linux freezing with agents in the terminal: the 32 GB zRAM that made things worse, /tmp in RAM with pnpm, and two-tier swap on Btrfs.
My laptop started freezing after I began running several AI agents in the terminal. The fans ran at maximum, the machine got hot, and the desktop froze in the middle of Google Meet calls. At the same time, /tmp sat at nearly 6 GB for no apparent reason.
The machine is a laptop with an Intel Core Ultra 5 125H, 16 GB of RAM and Arch Linux on Btrfs. The common reaction to running out of memory is to increase swap. I did that and raised zRAM to 32 GB. The result was the opposite of what I expected, because the freezes got worse.
Investigating the freezes
The first step was checking the logs of systemd-oomd, the daemon that kills processes before the kernel hangs from memory exhaustion. I found the record of the freeze:
systemd-oomd: Marked app-... for killing due to memory used (14.8G / 16.1G)
and swap used (30.9G / 34.3G) being more than 90.00%
Physical memory stood at 14.8 GB and swap at 30.9 GB. The machine had hit the combined RAM and swap threshold.
My swap lived entirely in zRAM. zRAM does not live on disk. It creates a compressed virtual memory block inside physical RAM.
On my system, zRAM used the zstd algorithm, with an average compression ratio of 3 to 1. When the agents filled 30.9 GB of virtual swap, the kernel needed about 10 GB of physical RAM just to hold the compressed pages.
The laptop has 15.5 GB of usable RAM, which left 5 GB for the desktop environment, the browsers, the video calls and the agents themselves. All 18 CPU threads competed for cycles compressing and decompressing memory with zstd. The CPU hit its thermal limit, dropped its clock, and the desktop froze.
The hidden accumulation in /tmp
The second problem was in /tmp. On Arch Linux, /tmp is mounted as tmpfs, which lives in RAM and can consume up to half of physical memory (7.6 GB).
Measuring the directories with du showed what used 5.8 GB of the partition:
/tmp/.pnpm-storewith 1.3 GB- 332 random Vitest directories adding up to 1.1 GB
- Git worktrees created by agents, with 1.8 GB
/tmp/node-compile-cachewith 467 MB
pnpm keeps packages in a global store and uses hard links to avoid duplicating files between projects. Hard links do not cross filesystems. When an agent creates a clone or worktree inside /tmp, pnpm detects that the folder is on tmpfs, not on /home. Unable to create the links, it downloads a full copy of every dependency into /tmp/.pnpm-store, which then takes 1.3 GB of RAM.
Each Vitest suite in SSR mode also created a temporary directory with a unique ID, and the files stayed behind when the run was interrupted.
The fixes
I split the solution into four parts: cleaning up /tmp, worktrees out of RAM, two-tier swap, and process and concurrency limits.
Worktrees out of RAM with automatic cleanup
The first fix was banning worktrees from /tmp. The default location became ~/.cache/worktrees/, on the main SSD. There, pnpm can hard link packages from the global store without using RAM.
So abandoned directories would not pile up, I created ~/.config/user-tmpfiles.d/worktrees.conf:
e %h/.cache/worktrees - - - 3d
I enabled the user systemd timer:
systemctl --user enable --now systemd-tmpfiles-clean.timer
The system now removes any directory in ~/.cache/worktrees not accessed for more than 3 days.
Two-tier swap with priorities
To stop the freezes without Google Meet calls dying from out-of-memory errors, I set up swap in two tiers:
flowchart LR accTitle: Two-tier swap accDescr: The kernel writes to zram0 first, 16 GB compressed in RAM at priority 100. The 32 GB file on the SSD, at priority 0, only receives data when zRAM fills up. K[Kernel] -->|writes first| Z["zram0<br/>16 GB in RAM<br/>lzo-rle · pri=100"] K -->|fallback| S["/swap/swapfile<br/>32 GB on the SSD<br/>Btrfs · pri=0"]
In Linux, the priority number decides where the kernel writes first. Since 100 is higher than 0, the system directs swap writes to zRAM. The file on the SSD stays at 0 bytes and only receives data when zRAM fills up. That is the safety margin.
I changed the zRAM algorithm from zstd to lzo-rle. lzo-rle compresses a little less, but costs far fewer CPU cycles.
On Btrfs with Snapper snapshots, a swapfile cannot live on the root subvolume, because snapshots do not support active swapfiles. I created a dedicated subvolume at /swap:
sudo btrfs subvolume create /swap
sudo btrfs filesystem mkswapfile --size 32g /swap/swapfile
sudo swapon --priority 0 /swap/swapfile
In /etc/fstab, the entry became:
/swap/swapfile none swap defaults,pri=0 0 0
Memory and concurrency limits for Node and Vitest
The heap ceiling of each Node.js process comes from --max-old-space-size, and the default sits around 4 GB. When agents run several lint and build scripts together, that adds up fast.
I set a 2 GB ceiling in ~/.config/environment.d/50-dev-limits.conf and in ~/.bashrc:
export NODE_OPTIONS="--max-old-space-size=2048"
export VITEST_MAX_THREADS=4
VITEST_MAX_THREADS=4 keeps Vitest from spawning 18 parallel workers during test runs and holds the CPU inside the laptop’s thermal envelope.
Concurrency rules for the agents
In the instructions file every one of my agents reads, ~/.agents/CONTEXT.md, I added an explicit rule:
- **Limite de concorrência em máquina local**: Nunca execute mais de 2 subagents simultaneamente quando a tarefa envolver compilação (tsc/build), execução de testes (vitest/jest/playwright) ou navegadores headless.
Results
After the changes, /tmp dropped from 5.8 GB to 120 MB in steady state.
With zRAM on lzo-rle and the 32 GB file on the SSD as the priority 0 fallback, the laptop stopped freezing and dropping Google Meet calls. The CPU temperature stayed in the normal range even with several test suites running together.