• chenyz

    github.com/anthropics/claude-code

    [BUG] Native updater on Windows silently skips the binary swap when consecutive releases have identical file sizes, and reports success

    已打开 01:14PM - 09 Aug 26 UTC

    图片

    AymanQ-A-Maani

    bug

    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?```

    The Windows native updater decides whether the installed launcher already matches
    the new build by comparing FILE SIZE ONLY. When two consecutive releases happen to
    compile to the exact same byte size, it concludes the launcher is already up to date,
    skips the copy entirely, and returns a result the rest of the update pipeline cannot
    distinguish from a real success.

    The result: claude update prints "Successfully updated from 2.1.225 to version 2.1.226",
    writes "outcome":"success" with "error_code":null to ~/.claude/.last-update-result.json,
    and leaves the launcher completely untouched. claude --version still reports the old
    version. Every subsequent claude update repeats the identical false success forever.

    This hit the 2.1.225 -> 2.1.226 pair, which are byte-identical in size:

    2.1.223 280,233,632 bytes
    2.1.224 284,981,920 bytes
    2.1.225 287,053,472 bytes
    2.1.226 287,053,472 bytes <-- same size as 2.1.225, different binary

    MD5 2.1.225 f7ad3f8bb03a3f54f96910126c91191e
    MD5 2.1.226 04df2e354071833ad36016e62cec7639

    The staged build in ~/.local/share/claude/versions/2.1.226 was downloaded correctly.
    Only the activation step (copying it over ~/.local/bin/claude.exe) was skipped.

    The false success is the worse half of this bug. A visible failure would be a minor
    annoyance; reporting success while doing nothing defeats every diagnostic path at once —
    the user-facing message, the update-result record, and the telemetry event
    (tengu_native_update_complete / tengu_native_auto_updater_success) all say it worked.
    These failures are therefore almost certainly being counted as successful updates in
    your metrics, so the true failure rate is invisible.

    Note: claude update --force does NOT help — the force flag controls download/reinstall,
    not the activation step where the size check lives. claude doctor does not surface it either.
    ``### What Should Happen?``
    claude update should either actually replace the launcher with the new build, or
    report a failure. It should never report success while leaving the old binary in place.

    Specifically:
    1. Equal file size must not be treated as proof of equal content. Compare a content
    hash, or compare the launcher's reported version against the target version, or
    simply always perform the swap.
    2. After activation, verify the launcher actually resolves to the target version and
    fail loudly if it does not. This single check would have caught the bug regardless
    of its cause, and would catch any future activation failure too.
    3. The skip path should emit its own telemetry outcome instead of being folded into
    the success count.
    ``### Error Messages/Logs``shell
    There is no error — that is the bug. For completeness:

    Terminal output of the failed update:

    Current version: 2.1.225
    Checking for updates to latest version...
    Updating to 2.1.226...
    Successfully updated from 2.1.225 to version 2.1.226

    Immediately afterwards:

    claude --version

    2.1.225 (Claude Code)

    ~/.claude/.last-update-result.json written by that run:

    {"timestamp":"2026-08-09T11:58:51.050Z","path":"native","outcome":"success",
    "status":"success","version_from":"2.1.225","version_to":"2.1.226","error_code":null}
    ``### Steps to Reproduce``
    1. On Windows with a native install, be on version A.
    2. Have version B released where sizeof(B) == sizeof(A) but B != A.
    (Real-world instance: A = 2.1.225, B = 2.1.226, both exactly 287,053,472 bytes.)
    3. Run claude update.
    -> It prints "Successfully updated from A to version B".
    4. Run claude --version.
    -> Still reports A.
    5. Confirm ~/.local/share/claude/versions/B exists and is the correct new binary,
    while ~/.local/bin/claude.exe still hashes identical to versions/A.
    6. Repeat step 3 any number of times — identical false success every time.

    Root cause, from the Windows branch of the launcher-activation function in the
    shipped 2.1.226 binary (minified):

    async function fTS(e, t) {
    if (Sxe().startsWith("win32")) try {
    let s = aS.dirname(e);
    await gf.mkdir(s, { recursive: !0 });
    let a;
    try { a = await gf.stat(e) } catch {}
    if (a) {
    try {
    let c = await gf.stat(t);
    if (a.size === c.size) return "noop"; // <-- BUG
    } catch {}
    let l = ${e}.old.${Date.now()};
    await gf.rename(e, l);
    try {
    await gf.copyFile(t, e);
    try { await gf.unlink(l) } catch {}
    } catch (c) { / restore / }
    }
    ...
    return "updated"
    } catch (s) { return "failed" }

    e is the launcher path, t is the staged new build. a.size === c.size treats equal
    file size as proof of equal content and returns "noop" before the rename-and-copy runs.

    The caller cannot tell "noop" from success:

    let l = await fTS(o, n);
    if (l !== "updated" && !await VKt(o)) { ...throw... }
    return { wasNewInstall: s, moveRetried: a,
    activationFailed: l === "failed",
    activationRefused: l === "refused" };

    With l === "noop" the launcher still exists and is a valid executable, so VKt(o) is
    truthy and nothing is thrown. activationFailed and activationRefused are both false.
    The caller returns {success: true, latestVersion: l}, logs "Successfully updated to
    version ...", which becomes wasUpdated: true and the "outcome":"success" record.

    The surrounding code already distinguishes "failed" and "refused" outcomes and has a
    rich error classifier including swap_failure and av_quarantine categories — the "noop"
    path bypasses all of it.
    ```### Claude Model

    _No response_

    Is this a regression?

    Yes, this worked in a previous version

    Last Working Version

    2.1.224

    Claude Code Version

    2.1.226

    Platform

    Anthropic API

    Operating System

    Windows

    Terminal/Shell

    VS Code integrated terminal

    Additional Information```

    Antivirus ruled out


    Including this because AV is the natural first suspicion for a Windows file-replacement
    failure. Kaspersky is installed on this machine. It is not involved:

    1. No file operation is ever attempted. The function returns "noop" before the
    rename/copy, so there is nothing for AV to block.
    2. The same operations succeed manually. Renaming the RUNNING claude.exe and copying
    the 287 MB staged build over it both completed without error, with Kaspersky active.
    (Renaming a running executable is permitted here, so the exe-lock path is not
    implicated either.)
    3. The product's own AV detection never fired — error_code was null and the outcome
    was "success", not "av_quarantine".

    Is it really a regression?


    Probably not in the code. The size-only shortcut appears in both 2.1.225 and 2.1.226,
    and updates 2.1.223 -> 2.1.224 -> 2.1.225 all applied correctly because those builds
    differed in size. So this looks like a long-standing latent bug that the 2.1.225/2.1.226
    size collision triggered for the first time, rather than something newly introduced.
    I answered the regression question as "yes" because updating did work for me previously,
    but the underlying defect is likely older than 2.1.225.

    Scope


    Any Windows native-install user whose installed version happens to match the byte size
    of the next release will be silently pinned to their current version indefinitely, with
    the CLI insisting the update succeeded. Since the size collision is a coincidence, the
    affected population will look random and intermittent from the outside, which will make
    it hard to correlate from telemetry — especially as these runs are recorded as successes.

    Workaround for affected users


    Copying the staged build over the launcher by hand restores the correct version:

    mv ~/.local/bin/claude.exe ~/.local/bin/claude.exe.bak
    cp ~/.local/share/claude/versions/<target> ~/.local/bin/claude.exe
    claude --version # verify

    Alternatively, removing the launcher entirely before updating takes the else-branch
    (copyFile with no size check) and applies correctly.
    ```原来真是bug,225升226是假替换,但是显示成功,还以为CC Switch的bug呢

    主楼
  • dragon_from_east

    你用的是啥模型,咋会这样呢?是CC订阅的吗

    #1
  • mawai

    我记得之前好像也有过update成功后还是老版本

    #2
  • chenyz

    我的意思是,我用命令升级,提示升级226成功,但是cc switch还是显示225 ^-^

    #3
  • gamecrafters

    正常,上次我claude update时提示的是225,但是实际展示的是226,应该是版本号写错了

    #4
  • dragon_from_east

    重启下看看,你CC的号是官方订阅的吗?还是用中转的吗

    #5
  • Soare02

    有两个claudecode吧,一个原生版本,就是官方那个irm的和npm安装的,这两个版本好像会同时存在而且可以版本不同,你让AI检查一下是否有2个版本。

    #6

发表回复

登录后回复