[BUG] WSL: package-manager installs are classified "native" — ownsExecutable returns before it can run dpkg -S / rpm -qf

Status Open
Reported on v2.1.267
Maintainer reply None cached
Activity 0 comments · opened Sep 19, 2026

Summary

On WSL, an install owned by the system package manager is classified as native, so the bundled self-updater takes over a binary the package manager owns. claude doctor reports Running: native for an apt install, and omits the Package manager: line it emits on non-WSL Linux.

Same shape as #89097: a platform-string branch that treats wsl and linux as disjoint where the Linux path is correct on WSL.

Root cause

In 2.1.267, every distro-package detector routes through ownsExecutable, which returns before it runs the ownership command (line breaks added, otherwise verbatim):

detectPacman(){return this.pacman??=this.ownsExecutable(["arch"],"pacman",["-Qo"])}
detectDeb(){return this.deb??=this.ownsExecutable(["debian"],"dpkg",["-S"])}
detectRpm(){return this.rpm??=this.ownsExecutable(["fedora","rhel","suse"],"rpm",["-qf"])}
detectApk(){return this.apk??=this.ownsExecutable(["alpine"],"apk",["info","--who-owns"])}

async ownsExecutable(e,n,i){
  if(this.sources.platform()!=="linux")return!1;
  let r=await this.getOsRelease();
  if(r&&!Z(r,e))return!1;
  let s=await this.sources.execFileNoThrow(n,[...i,this.sources.execPath()],{timeout:5000,useCwd:!1,useToolMemoryCgroup:!1});
  if(s.code===0&&s.stdout)return t(`Detected ${n} installation: ${s.stdout.trim()}`),!0;
  return!1
}

getPlatform() never yields linux under WSL:

else if(this.sources.platform==="linux")
  if(this.sources.env.WSL_DISTRO_NAME||this.sources.env.WSL_INTEROP)this.platform="wsl";
  else{let e=this.kernelString();this.platform=e!==void 0&&Nen(e)?"wsl":"linux"}

so all four detectors return false without executing, and the resolver falls through:

if(wht()||_()||F()||G()||await E()||await j()||await H()||await U())return"package-manager";
return"native"

detectHomebrew in the same class does accept WSL, so a Homebrew install on WSL is classified correctly while a dpkg-owned one is not:

detectHomebrew(){let e=this.sources.platform();if(e!=="macos"&&e!=="linux"&&e!=="wsl")return!1;

Steps to reproduce

  1. On WSL2, install via the apt repo (https://downloads.claude.ai/claude-code/apt/stable).
  2. Confirm the package manager owns the binary:
$ dpkg -S /usr/bin/claude
claude-code: /usr/bin/claude
  1. Run claude doctor:
Running: native (2.1.267)
Commit: a9e1808c8204
Platform: linux-x64
Path: /usr/bin/claude
Config install method: unknown
Search: OK (bundled)
Auto-updates: disabled (set by env: DISABLE_AUTOUPDATER)
Auto-update channel: latest
Last update attempt: none recorded

Running: native, and no Package manager: line — that field is only populated when the type resolves to package-manager. No native installation exists on the machine to account for it: ~/.local/bin/claude, ~/.local/share/claude, ~/.claude/local and ~/.claude/versions are all absent, and the Path above is the dpkg-owned binary from step 2. (DISABLE_AUTOUPDATER is set here as a local workaround for this bug and has no bearing on the classification.)

Contrast #87197, the same apt install on non-WSL Linux, where detection works: Running: package-manager, Package manager: deb, Auto-updates: Managed by package manager.

Expected behavior

On WSL, a dpkg/rpm/pacman/apk-owned install resolves to package-manager and the self-updater defers to the package manager, the same as on non-WSL Linux.

Suggested fix

Let ownsExecutable accept wsl alongside linux. The collapse already exists in the codebase:

function Wle(){let e=M(),n=e==="wsl"&&!Kc()?"linux":e;
function Hn(e){return e==="wsl"?["wsl","linux"]:[e]}

and the tool-memory cgroup path already tests both:

function Kn(e){let n=M();if(n!=="linux"&&n!=="wsl")return;

Environment

  • Claude Code 2.1.267 (a9e1808c8204), installed from the apt repo at /usr/bin/claude
  • WSL2, Debian 13 (ID=debian), linux-x64
  • Anthropic API

Symbol names are from the minified 2.1.267 bundle and will differ between builds.

View original on GitHub ↗