Skip to content

fix(cli): preserve claude as argv[0] when spawning the native binary - #1710

Open
snyderra wants to merge 1 commit into
slopus:mainfrom
snyderra:fix/preserve-claude-argv0
Open

fix(cli): preserve claude as argv[0] when spawning the native binary#1710
snyderra wants to merge 1 commit into
slopus:mainfrom
snyderra:fix/preserve-claude-argv0

Conversation

@snyderra

Copy link
Copy Markdown

Problem

Running Claude Code through happy makes it invisible to anything that identifies the running agent from argv[0].

findGlobalClaudeCliPath() calls resolvePathSafe() (i.e. fs.realpathSync) on every candidate, so on a native install the ~/.local/bin/claude symlink collapses to ~/.local/share/claude/versions/<version>. runClaudeCli() then spawns that path, and argv[0] becomes a bare version number.

Same machine, same Claude Code version, as seen by a terminal workspace manager that labels panes by the agent it detects in them:

$ claude          ->  argv0="claude"     name="2.1.237"   -> detected: claude, idle
$ happy claude    ->  argv0="2.1.238"    name="2.1.238"   -> detected: nothing
                      argv0="node" x3 (happy wrappers)

So the pane shows up as an unrecognized process under a couple of node wrappers. This also breaks plain pgrep claude and status-line integrations that key on the process name. Nothing about the session is actually different -- only the label the OS reports.

Fix

Pass argv0: 'claude' to the spawn in runClaudeCli(). cross-spawn forwards options straight through to child_process.spawn on POSIX, where argv0 is honored, so this is a one-option change with no behavioral effect on the child beyond the reported name.

Skipped on Windows: cross-spawn may route the call through cmd.exe, and overriding argv[0] there would misreport the command actually being run.

Verification

Before/after, spawning a stand-in binary that reports its own process.argv0:

before:  argv0 seen by spawned binary: /tmp/.../versions/9.9.9
after:   argv0 seen by spawned binary: claude

Confirmed on a real session too -- happy claude in a fresh pane goes from undetected to agent: claude, status: idle, and the agent's state (working / idle / blocked) tracks correctly from there.

Tests

Added a regression test to packages/happy-cli/scripts/claude_version_utils.test.ts that stands in for the native binary with a symlink to node (a shell script cannot be used here -- the kernel discards argv[0] for #! scripts) and asserts the spawned process sees claude. It fails on main with the version path and passes with this change.

vitest run --project unit scripts/claude_version_utils.test.ts -> 51 passed.

Note on the existing workaround

HAPPY_CLAUDE_PATH can work around this today, but only if it points at a regular file named claude that execs the real binary -- a symlink gets realpath'd away again by resolvePathSafe(). That is non-obvious enough that it probably should not be the requirement for being visible to a process-based detector.

🤖 Generated with Claude Code

`findGlobalClaudeCliPath()` realpaths every candidate it returns, so on a
native install `runClaudeCli()` spawns
`~/.local/share/claude/versions/<version>` and argv[0] becomes a bare version
number like `2.1.238`. Tools that identify the running agent from argv[0] --
terminal workspace managers that label panes by the agent they detect, status
lines, plain `pgrep claude` -- then see no claude at all, just an
unrecognized process under a couple of `node` wrappers. Launching Claude Code
directly keeps argv[0] as `claude`, so this shows up as "happy hides my
agent".

Pass `argv0: 'claude'` to the spawn. cross-spawn forwards options straight to
`child_process.spawn` on POSIX, where argv0 is honored. Skipped on Windows,
where cross-spawn may route the call through cmd.exe and overriding argv[0]
would misreport the command actually being run.

The existing `HAPPY_CLAUDE_PATH` escape hatch can work around this today (by
pointing at a regular file named `claude` that execs the real binary), but
only because that path is not itself a symlink -- which is not obvious, and
should not be required to be visible to a process-based detector.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant