Environment
- Package:
claude-desktop 1.15962.1-2.0.22 (upstream Claude 1.15962.1, port 2.0.22)
- OS: Ubuntu, native Linux x86_64, Electron 41.5.0
- Install: native Linux Electron (
/usr/lib/claude-desktop/...), not Wine
Summary
When the Cowork agent (Claude Code) runs a project hook declared in string form — a command with no args array — the bundled app.asar wraps it in cmd.exe unconditionally, with no process.platform check. On Linux cmd.exe doesn't exist, so the hook fails with:
"$CLAUDE_PROJECT_DIR"/scripts/worktree-setup.sh: Error: Failed to spawn cmd.exe: spawn cmd.exe ENOENT
This breaks any string-form hook on Linux (WorktreeCreate, SessionStart bash …, PreToolUse, …), not just the one above.
Root cause
In the bundled app.asar, the hook command runner (minified fn A5t) builds the spawn target like this (de-minified):
const hasArgs = hook.args !== undefined;
const [cmd, args] = hasArgs
? [expand(hook.command), hook.args.map(expand)] // exec form → run directly
: ["cmd.exe", ["/d", "/s", "/c", `"${hook.command}"`]]; // string form → cmd.exe, NO platform check
// … later: spawn(cmd, args, { cwd, stdin })
Searchable strings in app.asar: "/d","/s","/c" and process.env.comspec||"cmd.exe".
Note: the standalone Claude Code CLI and the VS Code extension run these same string-form hooks correctly on Linux (e.g. SessionStart: bash … hooks) — only this desktop bundle reaches cmd.exe.
Steps to reproduce
- Open a project whose
.claude/settings.json defines a string-form hook, e.g.:
"WorktreeCreate": [
{ "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/scripts/setup.sh" } ] }
]
- In Cowork, trigger it (e.g. create a new worktree session).
- →
Failed to spawn cmd.exe: spawn cmd.exe ENOENT.
Suggested fix (port-side patch)
The string-form branch should use the POSIX shell on non-Windows. A regex patch of app.asar (same spirit as the existing patch methodology, cf. #559) rewriting the hardcoded cmd.exe string-form spawn to "/bin/sh", ["-c", command] on Linux would resolve it. Worth reporting upstream too, so the runner picks the shell from process.platform.
User-side workaround
Declare the hook in exec form (command + args), which bypasses the cmd.exe branch entirely:
{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/scripts/setup.sh", "args": [] }
(The target script must be executable and have a shebang.)
Environment
claude-desktop1.15962.1-2.0.22(upstream Claude1.15962.1, port2.0.22)/usr/lib/claude-desktop/...), not WineSummary
When the Cowork agent (Claude Code) runs a project hook declared in string form — a
commandwith noargsarray — the bundledapp.asarwraps it incmd.exeunconditionally, with noprocess.platformcheck. On Linuxcmd.exedoesn't exist, so the hook fails with:This breaks any string-form hook on Linux (
WorktreeCreate,SessionStartbash …,PreToolUse, …), not just the one above.Root cause
In the bundled
app.asar, the hook command runner (minified fnA5t) builds the spawn target like this (de-minified):Searchable strings in
app.asar:"/d","/s","/c"andprocess.env.comspec||"cmd.exe".Note: the standalone Claude Code CLI and the VS Code extension run these same string-form hooks correctly on Linux (e.g.
SessionStart: bash …hooks) — only this desktop bundle reachescmd.exe.Steps to reproduce
.claude/settings.jsondefines a string-form hook, e.g.:Failed to spawn cmd.exe: spawn cmd.exe ENOENT.Suggested fix (port-side patch)
The string-form branch should use the POSIX shell on non-Windows. A regex patch of
app.asar(same spirit as the existing patch methodology, cf. #559) rewriting the hardcodedcmd.exestring-form spawn to"/bin/sh", ["-c", command]on Linux would resolve it. Worth reporting upstream too, so the runner picks the shell fromprocess.platform.User-side workaround
Declare the hook in exec form (
command+args), which bypasses thecmd.exebranch entirely:{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/scripts/setup.sh", "args": [] }(The target script must be executable and have a shebang.)