anthropics / anthropics/claude-code

macOS Desktop: ShipIt auto-update relaunch double-encodes non-ASCII environment variables for the whole app tree (UTF-8 read as Latin-1)

Offen
#93,265 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
area:desktop bug has repro platform:macos
Vorherrschende Sprache
Python
Sterne
145k
Forks
23.1k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

## Summary

On macOS, when Claude Desktop is relaunched by its Squirrel updater (`ShipIt`) after an auto-update, every environment variable whose value contains non-ASCII bytes arrives in the new process **double-encoded**: the original UTF-8 bytes are decoded as Latin-1 and re-encoded as UTF-8. The corrupted environment is then inherited by everything the app spawns, including Claude Code sessions in the Code tab and every shell the Bash tool runs.

launchd's stored value is *not* corrupted. Only the copy the relaunched app receives is. That is what makes this expensive to diagnose: `launchctl getenv` keeps showing the correct value while the tool running inside Claude Code fails on a path that "does not exist".

## Byte-level evidence

Variable set through a LaunchAgent, value containing CJK characters (`launchctl setenv MYVAR "/tmp/项目/x"`):

| where | bytes for `项目` | verdict |
|---|---|---|
| LaunchAgent plist on disk | `e9 a1 b9 e7 9b ae` | correct UTF-8 |
| `launchctl getenv MYVAR` | `e9 a1 b9 e7 9b ae` | correct |
| Terminal.app, launched normally | `e9 a1 b9 e7 9b ae` | correct |
| a second Electron app, launched via LaunchServices | `e9 a1 b9 e7 9b ae` | correct |
| **Claude.app, relaunched by ShipIt** | **`c3 a9 c2 a1 c2 b9 c3 a7 c2 9b c2 ae`** | **corrupted** |
| └─ Claude Helper | same corrupted bytes | inherited |
| └─ claude-code process | same corrupted bytes | inherited |
| └─ zsh spawned by the Bash tool | same corrupted bytes | inherited |

`e9` → `c3 a9`, `a1` → `c2 a1`, `b9` → `c2 b9` is exactly a Latin-1 decode followed by a UTF-8 encode. It is applied **exactly once**, at the moment `Claude.app` was exec'd. Child processes are not corrupted again on each spawn, so the damage is introduced by the relaunch itself, not by the app's process-spawning code.

The timing matches the updater:

```
Claude.app main process start: Wed Sep 9 03:31:55 2026
ShipIt_stderr.log: 2026-09-09 03:31:55.741 ShipIt[39126:4812207]
Successfully launched application at file:///Applications/Claude.app/
```

Control: a second Electron app on the same machine, reading the same launchd variable but launched normally through LaunchServices, received the correct bytes. Same source, different launcher, different result.

## Likely location

`Squirrel.framework/Resources/ShipIt` relaunches the app with `-[NSWorkspace launchApplicationAtURL:options:configuration:error:]` (that selector is present in the binary, alongside `processInfo`). If the configuration dictionary carries an `NSWorkspaceLaunchConfigurationEnvironment` built by reading the updater's own `environ` and decoding each byte as a character, that round trip produces exactly this fingerprint.

Suggested fix: do not hand the relaunch an environment dictionary at all, and let LaunchServices/launchd supply the environment the way a Dock launch does. If it must be passed explicitly, carry it as raw `char **` bytes and avoid any intermediate string decode.

Untested prediction that may help confirm the mechanism: if a user never manually quits the app, successive auto-updates should compound the encoding, producing `c3 83 c2 a9 …` after the second one. I only ever observed a single application, on a process whose parent had been launched cleanly from the Dock.

## Reproduction

1. `launchctl setenv MYVAR "/tmp/项目/x"` — any value with non-ASCII bytes works, CJK or accented Latin.
2. Quit Claude Desktop and relaunch it from the Dock. In the Code tab, `printenv MYVAR | xxd` shows the correct bytes.
3. Let the app auto-update, so ShipIt performs the relaunch.
4. In the Code tab afterwards, `printenv MYVAR | xxd` shows the double-encoded value.

The corruption persists for the lifetime of the process, so a session that lives through an auto-update keeps a corrupted environment indefinitely. Quitting and relaunching from the Dock clears it.

## Why it matters

The symptom that led me here: a CLI run from a Claude Code session printed

```
WARNING: proceeding, even though we could not create PATH aliases: CODEX_HOME points to
"/Volumes/.../Coding项ç\u{9b}®/..." but that path does not exist
```

while the identical command in Terminal.app worked, and `launchctl getenv CODEX_HOME` returned the correct path. Every diagnostic that looks at the configuration rather than at the running process's memory says the setup is fine.

Anyone whose home directory, project path, or any `PATH` entry contains non-ASCII characters is exposed, and the failure presents as a broken tool or a missing path rather than as a corrupted environment. Users in CJK and accented-Latin locales should hit this routinely.

Workaround for anyone who lands here: re-export the affected variables with correct bytes from `~/.zshenv`, so each shell overrides whatever it inherited, or quit the app and relaunch it from the Dock.

## Possibly related

`#92026` (UTF-8 CSV upload double-encodes Japanese text) and `#79482` (`/copy` writes the clipboard as Latin-1) look like the same decode-as-Latin-1 family surfacing elsewhere, though they are almost certainly different code paths from this one.

## Environment

- macOS 26.5.2 (25F84), arm64
- Claude Desktop 1.49585.0
- claude-code 2.1.260
- Squirrel.framework 1.0; `ShipIt` binary dated 2026-09-08

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Rechercherichtung

Start by inspecting Squirrel.framework/Resources/ShipIt and the NSWorkspace launchApplicationAtURL:options:configuration:error: entry point identified in the report. Reproduce with launchctl setenv and a non-ASCII value, then compare bytes before and after an auto-update; done means the relaunched app and its child processes preserve the original UTF-8 bytes.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
macos
Bereich
desktop-dev, operating-systems
Issue-Typ
Bug
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Aktiv
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.