[BUG] Claude Code escapes systemd transient scope at startup, defeating user-imposed memory caps
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Wrapping claude in systemd-run --user --scope -p MemoryMax=… does not bound Claude's memory usage. The process actively migrates itself out of the transient scope into the parent terminal's session scope within ~2 seconds of starting, leaving the scope empty and getting GC'd. The cap is then irrelevant. On a host where Claude's working set grows unbounded (e.g. context-heavy session, large MCP servers), it can OOM the entire machine instead of being OOM-killed inside its own cgroup. This is a real reliability/safety issue for users running Claude on workstations.
What Should Happen?
Claude should not write its PID to a different cgroup at startup. If a wrapper places Claude in a scope/slice with limits, those limits should hold for the entire process lifetime. At minimum, the migration behavior should be:
- documented,
- gated behind an opt-in flag, and
- disabled when the binary was launched via systemd-run --scope (detectable via $INVOCATION_ID or the initial cgroup path containing /run-p).
Error Messages/Logs
Environment
- Claude Code 2.1.144 (/home/.../.local/share/claude/versions/2.1.144, single ELF binary)
- Fedora, Linux 7.1.0-rc3, cgroups v2, user@1000.service active
- Launched from KDE Konsole via bash -l, with a wrapper function:
claude() {
local _bin="/home/me/.local/bin/claude"
if [ -S "${XDG_RUNTIME_DIR}/systemd/private" ]; then
systemd-run --user --scope --quiet \
-p MemoryMax=20G -p MemorySwapMax=0 -p TasksMax=500 \
-- "$_bin" "$@"
else
"$_bin" "$@"
fi
}
Steps to Reproduce
- Start Claude through a systemd-run --user --scope -p MemoryMax=… wrapper.
- Watch journalctl --user -f and the process cgroup:
pid=$(pgrep -f /home/me/.local/bin/claude)
awk -F: '{print $3}' /proc/$pid/cgroup
- Observe:
- Scope is created: Started run-pPID-iXXXX.scope - [systemd-run] … claude
- ~2 s later: run-pPID-iXXXX.scope: Consumed Ns CPU time over Ns wall clock time, NNNM memory peak.
- Claude PID is unchanged and still running, but now reports cgroup …/app-org.kde.konsole-.scope/tab().scope.
- The transient scope has been removed.
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.144
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
Other
Additional Information
A user trying to defensively limit Claude's blast radius on a shared workstation cannot. The single user-space mitigation that should "just work" on a modern Linux desktop — wrapping the command in a transient systemd scope — is silently defeated by the process itself. The only remaining options are slice-level limits on user.slice (over-broad) or RLIMIT_AS (imprecise for V8). A runaway Claude takes down the whole host.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗