[BUG] Claude Code hangs indefinitely in epoll_pwait loop on gVisor ARM64 (macOS Docker Desktop - OrbStack)

Resolved 💬 3 comments Opened Mar 17, 2026 by kstenerud Closed Apr 14, 2026

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 --print flag (non-interactive mode)
  • Output redirected to files
  • HOME and 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

  1. 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
``

  1. Build the image:

``bash
docker build -t claude-gvisor-test .
``

  1. 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
``

  1. Observe: Process starts but produces zero output and hangs indefinitely
  1. 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 --version works (exits quickly, no session startup)
  • claude --dangerously-skip-permissions hangs (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:

  1. File descriptors reported as negative numbers (-38) - gVisor ARM64 ptrace bug
  2. Process repeatedly reads git files (.git/HEAD, .git/config, .git/refs/heads/main)
  3. epoll_pwait() consistently returns 0 events and is called again immediately
  4. 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_wait doesn't return EPOLLHUP events on remote close
  • gVisor #9816: Slow network performance with epoll_wait delays
  • 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).

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗