Blog · September 21, 2026 · 8 min read
CLAUDE_CODE_DISABLE_BG_SHELL_PRESSURE_REAP Explained
You leave Claude Code running a long build in the background, come back forty minutes later, and find every background task dead. Not one of them — all of them, killed in the same second. The transcript says Background command "…" was stopped and the status reads killed. There is no error, no OOM message in the journal, and nothing in your own history that stopped anything.
If that sounds familiar, the thing you are looking for is called CLAUDE_CODE_DISABLE_BG_SHELL_PRESSURE_REAP. It is an undocumented environment variable, it is almost certainly why your tasks died, and setting it to 1 stops it happening.
This post explains what the reaper is, why it fires on machines that have plenty of free memory, how to tell whether it is your problem, and the honest case for and against turning it off.
What the reaper does
Claude Code registers a listener for the runtime's memoryPressure event whenever you have a background shell task running. When that event fires, every eligible running background task in the session is marked killed and its process tree is torn down — simultaneously, because one event reaches every registered listener at once.
The intent is reasonable: background shells are the easiest thing to sacrifice when a machine starts thrashing, and killing them is better than having the whole session die. The variable is the guard on that registration. Set it and the listener is never attached, so the reaper cannot fire.
One important detail: this is not the same as CLAUDE_CODE_DISABLE_BACKGROUND_TASKS. That one turns backgrounding off entirely. This one keeps backgrounding and removes only the memory-pressure kill.
Why it fires on a machine with 39 GB free
This is the part that makes the behaviour so confusing, and it comes down to what "memory pressure" means on Linux.
The runtime does not poll free memory. On Linux it arms a PSI trigger on /proc/pressure/memory. Community analysis of the shipped binary in the open GitHub issue tracking this found the trigger spec to be some 150000 2000000 — fire when any task is stalled on memory for 150 ms within any 2-second window.
That has three consequences that explain everything odd about it:
MemFreeandMemAvailableare irrelevant. A busy server with 31 GB of page cache and 39 GB "available" produces transient reclaim stalls as a matter of routine. Those stalls are what the trigger sees.- The PSI averages will look clean afterwards. A trigger fires on the raw 2-second window, before
avg10andavg60move. So you check/proc/pressure/memoryafter the kill, readavg10=0.00, and conclude memory pressure could not possibly have been the cause. It was. - A loaded box triggers it far more than a low-memory box. High load with heavy I/O is a better predictor than actual memory scarcity.
The gates: why it hits idle sessions specifically
The reaper does not fire on every pressure event. From the same analysis of versions 2.1.212 and 2.1.251, a task is only reaped when all of these hold:
- The task is still running and has not already been notified.
- The last user interaction was more than 30 minutes ago. Harness-injected turns — background task notifications, hook output — do not reset that clock.
- The main loop is idle.
- No live non-shell task (agent or teammate) is in the registry.
That 30-minute idle gate is the single most useful fact here. It is why the reaper never bothers you during active work and reliably eats the overnight run. It is also why headless and long-running automated sessions are permanently past the gate after their first half hour.
One more: only main-thread tasks register the listener. Background tasks started by a subagent are not pressure-reaped.
How to tell whether this is what happened to you
Frustratingly, you largely cannot — and that is a known complaint in the issue. The reaper emits a telemetry event rather than a debug-log line, so the only client-side artifact is the same generic LocalShellTask <id> kill requested line that a user stop, an expired monitor, or a UI stop produces. Running with DEBUG=1 will not confirm it.
What you can go on is the signature, which is distinctive:
- Every live background task in a session dies in the same second. A single task dying alone is something else.
- Tasks in different sessions die within milliseconds of each other.
memoryPressureis a host-wide signal, so nothing per-session can produce that. - The session was idle, and had been for over half an hour.
- Nothing in
journalctlordmesgat that instant — no OOM killer, nosystemd-oomd. - Child process logs end mid-stream, with no error of their own.
If four of those five match, set the variable and see whether the problem disappears. That is a faster test than any amount of instrumentation.
How to set it
For a single session:
export CLAUDE_CODE_DISABLE_BG_SHELL_PRESSURE_REAP=1
claudeTo make it permanent, put it in the env block of your settings.json rather than your shell profile, so it applies to every launch including ones you did not start from a terminal:
{
"env": {
"CLAUDE_CODE_DISABLE_BG_SHELL_PRESSURE_REAP": "1"
}
}Like every Claude Code environment variable, it is read at launch, so restart the session after changing it.
Should you set it?
The honest answer is that it depends on whether your machine has real memory headroom, because turning the reaper off removes a safety valve.
Set it if: you run Claude Code on a server or a workstation with plenty of RAM, you use long background tasks (test suites, builds, data jobs), and you have seen the signature above. The reaper is firing on ordinary reclaim activity rather than on genuine scarcity, so you are losing work to a false positive.
Leave it alone if: you are on a laptop with 8 or 16 GB, you run several agents at once, and your background tasks are short. On a machine that genuinely runs out of memory, having Claude shed background shells is better than having the editor, the browser and the session all stall together. Disabling the reaper does not make the memory appear; it just moves who pays.
The middle path is worth naming, because it is what most people actually want: keep the reaper on, but stop being surprised by it. The reason these kills cost so much is that they happen while you are away and you only discover them forty minutes later. A notification when a run finishes or dies closes that gap regardless of what killed it.
The wider point
This variable belongs to a small family of escape hatches for the automatic behaviours that make Claude Code pleasant interactively and awkward when it is running unattended. Auto-backgrounding, background-task reaping, and idle handling are all tuned for someone sitting at the terminal.
If you are running longer autonomous sessions, it is worth reading them as a set: our guides on background tasks and disabling them entirely cover the other two halves of the same trade-off.
And a caveat worth stating plainly: this variable is not in the official environment-variable reference. Everything above comes from an open issue and from people reading the shipped bundle. The workaround has held across several versions, but undocumented behaviour can change without a changelog line — so if your tasks start dying again after an update, check whether the guard still exists before assuming something else broke.
Keep reading
Claude Code Tips and Tricks: 12 Ways to Use Claude Code Effectively
8 min read
Claude Code Multiple Sessions: How to Run Agents in Parallel Without Losing Track
6 min read
A Claude Code Workflow That Doesn't Involve Watching the Terminal
5 min read
Claude Code Hooks: A Practical Guide to Automating Your Agent Workflow
7 min read
Claude Code Auto Mode: Fewer Permission Prompts Without Living Dangerously
7 min read
Claude Code Subagents: How to Delegate Work to Specialized Agents
7 min read
Claude Code Context Management: Treat the Context Window Like a Budget
6 min read
Per-Subagent Model Selection: Route the Grunt Work Down, Keep the Judgment Up Top
7 min read
Skills and Plugins: How to Teach Claude Code Your Way of Working
7 min read
Claude Code Background Tasks: Run Long Commands Without Blocking Your Session
6 min read
Codex CLI Notifications: How to Get a Ding When Codex Is Done or Needs Input
7 min read
Cursor Notification When Done: Every Way to Get Notified When Cursor Finishes
6 min read
Claude Code Notifications: How to Get Notified When Claude Code Finishes or Needs Your Input
6 min read
Want to Be Notified When Claude Responds? How Claude Notifications Work on Web, Desktop, and Mobile
5 min read
Claude Code Notification Scripts: Copy-Paste Recipes for Every Platform
6 min read
Get a Ding the Moment Codex Needs Your Response
6 min read
Get Notified the Moment Claude Code Is Waiting for Your Input
6 min read
“Notifications Are Turned Off for Claude” — Here Is the Fix
6 min read
Codex Sound When Done: Make Codex CLI Play a Sound When It Finishes
6 min read
terminal-notifier + Claude Code: Native macOS Alerts When Your Agent Finishes
6 min read
Gemini CLI Notifications: How to Get a Sound or Alert When Gemini Finishes
6 min read
Get Claude Code Notifications on Your Phone
6 min read
Claude Code Remote Control, Explained
7 min read
preferredNotifChannel: Claude Code's Built-In Notification Setting
6 min read
Claude Code Notifications in tmux and Over SSH
7 min read
Claude Code Effort Levels: Why Your Setting Keeps Getting Ignored
8 min read
Codex vs Claude Code Notifications: How Each One Tells You It Is Done
8 min read
Claude Code Notifications Not Working: A Diagnostic Checklist
8 min read
Claude Code Notifications Inside Your Editor's Terminal
7 min read
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS Explained
7 min read