Skip to content

[bug]: Claude Code string-form hooks fail on Linux — spawned via cmd.exe (spawn cmd.exe ENOENT) #756

Description

@maximevct

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

  1. 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" } ] }
    ]
  2. In Cowork, trigger it (e.g. create a new worktree session).
  3. 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.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcoworkRelated to Cowork modeplatform: amd64Affects x86_64 architecturepriority: highImportant, should be addressed soontriage: investigatedIssue has been triaged and investigated

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions