[BUG] Claude Code hangs indefinitely in epoll_pwait loop on gVisor ARM64 (macOS Docker Desktop - OrbStack)
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 starts successfully but produces zero terminal output and hangs indefinitely when run inside a Docker container using the gVisor (runsc) runtime on macOS ARM64 (Apple Silicon) with Docker Desktop.
The process starts (visible in ps, 238MB RSS, state=S), but blocks forever in an infinite epoll_pwait() loop before producing any output. The hang occurs even when:
- Run without a TTY (
< /dev/null) - Run with
--printflag (non-interactive mode) - Output redirected to files
HOMEand environment variables are correctly set
Critical difference from #27230: That issue shows Claude exiting cleanly after 5 seconds. This issue shows Claude hanging forever in syscall.
What Should Happen?
Claude Code should start normally and display its interactive prompt, just as it does when running:
- In regular Docker containers (without gVisor)
- On the host machine directly
- With gVisor on x86_64 architecture
Error Messages/Logs
**No error messages** - process hangs silently with zero output.
Debug mode (`--debug`) shows:
$ docker exec container claude --debug --dangerously-skip-permissions
node:fs:2380
return binding.writeFileUtf8(
^
Error: ENOENT: no such file or directory, open '/.claude/debug/b86d4a43-01f5-4ea1-8e6b-610b86c22923.txt'
(Tries to write debug log to `/.claude/debug/` which doesn't exist, then crashes)
Steps to Reproduce
Prerequisites
- macOS ARM64 (Apple Silicon - M1/M2/M3)
- Docker Desktop with gVisor runtime configured
- Claude Code CLI installed
Reproduction Steps
- Create a Dockerfile with Claude Code:
``dockerfile``
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y curl ca-certificates
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
RUN apt-get install -y nodejs
RUN npm install -g @anthropic-ai/claude-code@latest
RUN useradd -m -u 1001 -s /bin/bash testuser
USER testuser
WORKDIR /home/testuser
- Build the image:
``bash``
docker build -t claude-gvisor-test .
- Run with gVisor runtime:
``bash``
docker run -it --rm \
--runtime=runsc \
-e CLAUDE_CODE_OAUTH_TOKEN="your-token-here" \
claude-gvisor-test \
claude --dangerously-skip-permissions
- Observe: Process starts but produces zero output and hangs indefinitely
- Compare with regular Docker (no gVisor):
``bash``
docker run -it --rm \
-e CLAUDE_CODE_OAUTH_TOKEN="your-token-here" \
claude-gvisor-test \
claude --dangerously-skip-permissions
This works correctly and shows Claude's interactive prompt.
Minimal Test Without Docker
If you have gVisor + Docker Desktop on ARM64 macOS, you can verify with just:
docker run -it --rm --runtime=runsc \
-e CLAUDE_CODE_OAUTH_TOKEN="$(cat ~/.claude/.credentials.json | jq -r '.access_token')" \
node:20-slim bash -c "npm install -g @anthropic-ai/claude-code && timeout 10 claude --version"
claude --versionworks (exits quickly, no session startup)claude --dangerously-skip-permissionshangs (full session startup)
Diagnostic Information
strace Analysis
Running strace -f claude --dangerously-skip-permissions shows:
[pid 7827] epoll_pwait(-38, [], 1024, 993, NULL, 8) = 0
[pid 7827] epoll_pwait(-38, [], 1024, 992, NULL, 8) = 0
[pid 7827] epoll_pwait(-38, [], 1024, 991, NULL, 8) = 0
... (infinite loop, always returns 0 events)
Key observations:
- File descriptors reported as negative numbers (
-38) - gVisor ARM64 ptrace bug - Process repeatedly reads git files (
.git/HEAD,.git/config,.git/refs/heads/main) epoll_pwait()consistently returns 0 events and is called again immediately- No actual syscall failure, just waiting for events that never arrive
What We've Ruled Out
Extensive testing shows this is NOT caused by:
- ❌ PTY/terminal issues (hangs even with
< /dev/null) - ❌ Missing authentication (token is properly mounted and accessible)
- ❌ Missing HOME directory (exists at
/home/testuser/.claude/with correct files) - ❌ Network issues (can reach api.anthropic.com via curl)
- ❌ Basic Node.js functionality (async I/O, readline, TTY ops, signals all work)
- ❌ Missing syscalls (gVisor ARM64 reports epoll as fully implemented)
Environment Details During Hang
# Process is running
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
testuser 34 0.2 2.8 9279300 238732 ? Ssl 18:11 0:04 claude
# No output captured (even after minutes)
$ docker exec container cat /tmp/claude.out
(empty)
# Can create files in .claude directory
$ docker exec container su - testuser -c 'echo test > ~/.claude/test.txt'
$ docker exec container cat /home/testuser/.claude/test.txt
test
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.77 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
Workaround
Running with standard Docker (no gVisor) works perfectly:
docker run -it --rm \
-e CLAUDE_CODE_OAUTH_TOKEN="..." \
claude-gvisor-test \
claude --dangerously-skip-permissions
Comparison with Issue #27230
| Aspect | Issue #27230 | This Issue |
|--------|--------------|------------|
| Symptom | Exits cleanly after ~5s | Hangs indefinitely |
| Exit Code | 0 (success) | N/A (never exits) |
| Platform | Not specified | macOS ARM64 Docker Desktop |
| Root Cause | Event loop drains | Event loop blocks waiting for events |
| Syscall | Not specified | Stuck in epoll_pwait |
Both issues are gVisor-related event loop problems but with opposite symptoms (exit vs hang).
Relevant gVisor Issues
- gVisor #7331:
epoll_waitdoesn't returnEPOLLHUPevents on remote close - gVisor #9816: Slow network performance with
epoll_waitdelays - nodejs #55587: Node processes hang in uninterruptible deep sleep on epoll_pwait
Hypothesis
gVisor ARM64's epoll_pwait implementation may have a bug where it fails to deliver events that should wake the event loop. The negative file descriptors visible in strace (-38 instead of normal fds like 3 or 4) suggest gVisor has issues with file descriptor management on ARM64.
The fact that claude --version works (quick, no event loop startup) but full session startup hangs suggests the issue is in how gVisor handles the Node.js/Bun event loop during initialization, possibly related to the io_uring fallback to epoll mentioned in #27230.
Files for Reference
Full diagnostic investigation documented at:
- strace output showing infinite epoll loop
- Comparison with working regular Docker setup
- Tests of basic Node.js event loop features (all pass)
Available on request.
---
Impact: This blocks using Claude Code in gVisor-sandboxed environments on ARM64 macOS, which is increasingly common for security-conscious development workflows on Apple Silicon.
Severity: High for ARM64 macOS users wanting container security, but has workaround (use standard Docker runtime).
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗