[BUG] claude update hangs indefinitely (100s+) when falling back from reflink to copy_file_range across filesystems, no timeout or progress indication
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?
Environment
OS: Linux, tested with Ubuntu and Arch.
Install type: native, custom launcher script (reads highest version-sorted entry under a versions directory, per the pattern described in the "Advanced setup" docs for replacing the managed launcher)
Filesystem layout:
$HOME (/home/chris) — NFS
$HOME/.cache — bind-mounted to local scratch storage (/scratch/local)
A separate NFS mount also in use for the actual installed versions directory (/work/chris/...)
What happened
claude update reported "Updating to 2.1.217..." and then hung indefinitely. Two separate runs hung for several minutes before being interrupted; a third run was captured under strace -f -o claude.strace claude update with a 120s timeout wrapper.
Root cause (from strace)
Download completes fast — the full update payload (~256 MB) arrives over HTTPS in about 2 seconds. Not a network issue.
The updater stages the downloaded file under $HOME/.cache/claude/staging/... (local filesystem in our case).
It attempts to install via reflink:
ioctl(19, BTRFS_IOC_CLONE or FICLONE, 16) = -1 EXDEV (Invalid cross-device link)
This fails because staging (.cache, local) and the destination (.local/share/claude/versions, NFS) are on different filesystems — expected and correctly detected.
- It falls back to a full copy:
13:36:37 copy_file_range(16, [0], 19, [0], 268573680, 0 <unfinished ...>
13:38:34 <... copy_file_range resumed>) = ? ← process killed by external timeout here, never returned
This single syscall never returns. It blocks for the entire remainder of the run (117+ seconds and counting when killed) with no timeout, no chunking/progress, and no way to tell from the CLI's output that this is what's stuck.
Why this matters
Falling back from reflink to copy_file_range on EXDEV is the right behavior — cross-filesystem $HOME/.cache vs $HOME/.local/share is a normal setup (common on HPC/academic clusters, and anywhere .cache is pointed at local/fast scratch storage on purpose). But the fallback path has no upper bound on how long it can block.
There is no user-visible indication that a large file copy is in progress, so a legitimately slow or temporarily stalled destination filesystem is indistinguishable from a hung/broken updater.
Interrupting the process mid-copy leaves partial state behind (e.g. a stale versions/<version> file from a previous attempt caused EEXIST on the next run, before the updater moved to a uniquely-suffixed temp filename — so this is at least partially handled, but the situation is still fragile).
Suggested fixes
Add a timeout (and/or periodic progress reporting) around the copy_file_range fallback path, with a clear error message if it's exceeded, rather than blocking silently.
Show visible progress during the install/copy phase distinct from the download phase, so users can tell which step is slow.
Consider allowing the final rename() to happen within whichever filesystem the staging directory is actually on (i.e., install location follows the writable/fast mount rather than always targeting $HOME/.local/share), when that's configurable or detectable.
Repro
Have $HOME/.cache and $HOME/.local/share on different filesystems where the latter is NFS with degraded/slow write throughput, and run claude update.
What Should Happen?
It should correctly copy the file
Error Messages/Logs
Steps to Reproduce
Have $HOME/.cache and $HOME/.local/share on different filesystems where the latter is NFS with degraded/slow write throughput, and run claude update.
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.215 (I need previous because newer doesn't upgrade because it's newest)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Xterm
Additional Information
_No response_