[BUG] 2.1.128 deterministically hangs during settings init under libkrun + uid-shifted virtio-fs
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
Starting in 2.1.128, claude deterministically hangs during early init when run inside a libkrun microVM (podman + crun's run.oci.handler=krun) with --userns=keep-id. The CLI never produces output, never exits on its own, and is killable only by an external timeout. Same image, same flags, with 2.1.126 — works fine. Same image with 2.1.128 but without --userns=keep-id — also works fine. The hang requires the conjunction.
The last debug-log line before the hang is always the same Programmatic settings change notification for policySettings event, followed by the four broken-symlink probes for settings.json candidates. After that, the process drops into a pure event-loop poll (epoll_pwait2 + futex + clock_gettime + madvise) and never resumes.
What Should Happen?
claude -p "say pong" should produce a streamed response within a few seconds, exactly as 2.1.126 does in the same environment, and as 2.1.128 itself does without --userns=keep-id.
Error Messages/Logs
# /work/d.log — last 5 lines from a hung run (full log = 70 lines, all of it pre-divergence-point)
2026-05-05T06:11:27.866Z [DEBUG] Initializing Plugins
2026-05-05T06:11:27.868Z [DEBUG] Programmatic settings change notification for policySettings
2026-05-05T06:11:27.869Z [DEBUG] Broken symlink or missing file encountered for settings.json at path: /home/node/.claude/settings.json
2026-05-05T06:11:27.869Z [DEBUG] Broken symlink or missing file encountered for settings.json at path: /work/.claude/settings.json
2026-05-05T06:11:27.870Z [DEBUG] Broken symlink or missing file encountered for settings.json at path: /work/.claude/settings.local.json
2026-05-05T06:11:27.871Z [DEBUG] Broken symlink or missing file encountered for settings.json at path: /etc/claude-code/managed-settings.json
# (no further log lines; process hangs until SIGTERM from outer timeout)
# Comparison: 2.1.126 in the same environment writes ~184 lines and exits 0.
Symptom matrix (3 trials each, exact same Dockerfile/host/flags differing only in the marked dimension):
| claude version | krun, no --userns | krun + --userns=keep-id |
|----------------|---------------------|---------------------------|
| 2.1.126 | works (184 lines) | works (184 lines) |
| 2.1.128 | works (185 lines) | hangs (70 lines, exit 124) |
Strings present in claude.exe for 2.1.128 but absent in 2.1.126 that look related (extracted via strings on the bundled binary):
migration_bypass_permissions_to_settingsmigration_auto_updates_to_settingsmigration_mcp_servers_to_settingsmigration_user_intent_to_settingssandbox_set_settingsgetPolicySettingsLoadErrorsCannot destructure property 'getSettingsForSource' from null or undefined value
These suggest 2.1.128 introduced a new settings-migration / policy-settings code path; the hang sitting immediately after the policySettings notification + symlink probes is consistent with that path being where it stalls. Pre-seeding ~/.claude.json with migrationVersion: 13 from a prior successful run does not prevent the hang.
Steps to Reproduce
Host: Fedora 44, podman 5.x rootless, crun 1.27.1 with +LIBKRUN, libkrun 1.17.4, libkrunfw 5.3.0, kernel 6.12.
- Build a minimal image with v2.1.128:
``dockerfile``
# Dockerfile
FROM docker.io/library/node:22-bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/*
RUN npm install -g @anthropic-ai/claude-code@2.1.128 && claude --version
ENTRYPOINT ["/bin/bash"]
````
podman build -t claude-repro:128 .
- Run under krun without
--userns=keep-id— this works:
````
podman run --rm \
--runtime crun --annotation run.oci.handler=krun \
--entrypoint /bin/bash claude-repro:128 \
-c 'timeout 25 claude -p "say pong" --debug-file /tmp/d.log </dev/null; echo "exit=$?"; wc -l /tmp/d.log'
# → exit=0, ~185 debug-log lines, response received
- Add
--userns=keep-id— same image, same command otherwise — hangs:
````
podman run --rm \
--runtime crun --annotation run.oci.handler=krun \
--userns=keep-id \
--entrypoint /bin/bash claude-repro:128 \
-c 'timeout 25 claude -p "say pong" --debug-file /tmp/d.log </dev/null; echo "exit=$?"; wc -l /tmp/d.log'
# → exit=124 (timeout fired), 70 debug-log lines, no output
- Swap the version pin to
2.1.126in step 1, rebuild, repeat step 3 — works fine. The regression is what step 3 does on 2.1.128 vs 2.1.126.
Reproduces in 3/3 trials, every time, on a clean state.
Claude Model
Not sure / Multiple models (headless claude -p doesn't reach the model selector — the hang is well before any API call)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.126
Claude Code Version
2.1.128 (Claude Code)
Platform
Anthropic API
Operating System
Other Linux (Fedora 44 host; container is node:22-bookworm-slim — Debian)
Terminal/Shell
Non-interactive/CI environment
Additional Information
Workaround: drop --userns=keep-id from the podman run invocation. Inside a krun microVM, --userns=keep-id is mostly cosmetic anyway — the guest has its own kernel and virtio-fs presents files as root regardless of the host-side mapping — so the only consequence of dropping it is that bind-mounted files written by the guest land owned by the rootless subuid range on the host, easy to fix up with chown if needed.
Why I think it's specifically the new settings-migration code: I diffed the bundled claude.exe between 2.1.126 and 2.1.128 by string-table extraction (binaries are sha256-different but the public binding patterns are visible). The strings listed above are the only group I found that corresponds to a new init-time code path with plausible filesystem behaviour. An strace of the hung process shows no further filesystem syscalls after the symlink probes — just epoll/futex/clock_gettime — so whatever it's waiting for is internal to the JS runtime (Bun) rather than an outstanding I/O request, and it never times out on its own.
If a maintainer wants to dig in: the cleanest signal would be adding debug logging immediately around whatever runs after Programmatic settings change notification for policySettings, particularly any settings-migration code that walks user dirs or watches for symlink targets. Happy to provide captured claude --debug-file logs from both passing and hanging cases, or run additional probes.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗