[BUG] PR_SET_THP_DISABLE=1 is inherited into spawned children, breaking applications that rely on THP (devdax SIGBUS)
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?
Claude Code runs on Node.js, and V8 calls prctl(PR_SET_THP_DISABLE, 1) on startup to disable transparent huge pages for itself. That prctl flag is inherited across fork/execve, so every child process spawned from Claude Code (Bash tool, sub-agents, user binaries) also has THP disabled, whether or not it wants to.
This silently breaks any workload that actually requires PMD-sized faults.
The most visible failure mode is devdax mmap with align=2M: on such devices the kernel only implements a PMD fault path, so with PR_SET_THP_DISABLE=1 the very first access to the mapping raises SIGBUS.
Root cause
V8 disables THP for its own process on startup:
- V8 source:
src/base/platform/platform-linux.cccallsprctl(PR_SET_THP_DISABLE, 1, ...)to avoid khugepaged latency spikes during GC. - Linux semantics: per
prctl(2), thePR_SET_THP_DISABLEflag is inherited by child processes created viafork(2)and preserved acrossexecve(2).
Most user code has no reason to ever call that prctl and will simply misbehave:
- devdax
align=2Mmappings: immediateSIGBUSon first touch. - Regular anonymous THP workloads: silent TLB-miss regressions (harder to notice, but same root cause).
Impact
- Any code path under Claude Code that touches a 2 MiB-aligned devdax device crashes on first access (to be honest, Most user doesn't affect ).
- Silent performance degradation for any THP-sensitive workload launched from Claude Code.
- Hard to diagnose from the user side: the failing binary looks buggy, but
straceshows a cleanmmapfollowed by aSIGBUS, and the only hint isgrep THP_enabled /proc/self/statusreturning0.
What Should Happen?
Children spawned by Claude Code's tools should not silently inheritPR_SET_THP_DISABLE=1. Either:
- child processes are started with the flag cleared, or
- the user-facing Bash tool clears it before
execve, or - at minimum, this is documented so affected workloads can work around it.
Error Messages/Logs
Steps to Reproduce
Minimal reproducer
/*
* Devdax (align=2 MiB) SIGBUSes on the first fault when the process has
* PR_SET_THP_DISABLE=1. Flipping it back makes the same access work.
*
* cc -O2 -Wall -o pr_thp_devdax_sigbus pr_thp_devdax_sigbus.c
* ./pr_thp_devdax_sigbus [/dev/dax0.1]
*/
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <unistd.h>
static void child(int restore, const char *path) {
if (restore) prctl(PR_SET_THP_DISABLE, 0, 0, 0, 0);
printf(" PR_GET_THP_DISABLE=%d\n", prctl(PR_GET_THP_DISABLE, 0, 0, 0, 0));
int fd = open(path, O_RDWR);
void *p = mmap(NULL, 2UL << 20, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
*(volatile unsigned char *)p = 0xA5;
_exit(0);
}
static void run(const char *tag, int restore, const char *path) {
printf("%s:\n", tag); fflush(stdout);
pid_t pid = fork();
if (pid == 0) child(restore, path);
int st; waitpid(pid, &st, 0);
printf(" -> %s\n",
WIFSIGNALED(st) ? strsignal(WTERMSIG(st)) : "ok");
}
int main(int argc, char **argv) {
const char *path = argc > 1 ? argv[1] : "/dev/dax0.1";
setlinebuf(stdout);
run("A (inherited)", 0, path);
run("B (prctl PR_SET_THP_DISABLE=0)", 1, path);
return 0;
}
Steps
- Build:
cc -O2 -Wall -o pr_thp_devdax_sigbus pr_thp_devdax_sigbus.c - Run it directly from a normal shell:
````
$ ./pr_thp_devdax_sigbus /dev/dax0.1
- Run it again from inside Claude Code's Bash tool on the same machine:
````
(claude) $ ./pr_thp_devdax_sigbus /dev/dax0.1
Observed
From a plain shell (both phases fine, as expected):
A (inherited):
PR_GET_THP_DISABLE=0
-> ok
B (prctl PR_SET_THP_DISABLE=0):
PR_GET_THP_DISABLE=0
-> ok
From inside Claude Code's Bash tool — phase A dies with SIGBUS:
A (inherited):
PR_GET_THP_DISABLE=1
-> Bus error
B (prctl PR_SET_THP_DISABLE=0):
PR_GET_THP_DISABLE=0
-> ok
Phase B always succeeds, which pins the cause to the inherited prctl flag rather than the device, the kernel, or the binary.
Claude Model
None
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.118
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
_No response_
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗