Skip to content

fix(cli-resolve): expand leading ~ in CLAUDE_CODE_PATH before spawn#3275

Draft
posthog[bot] wants to merge 2 commits into
mainfrom
posthog-code/expand-tilde-claude-code-path
Draft

fix(cli-resolve): expand leading ~ in CLAUDE_CODE_PATH before spawn#3275
posthog[bot] wants to merge 2 commits into
mainfrom
posthog-code/expand-tilde-claude-code-path

Conversation

@posthog

@posthog posthog Bot commented Jul 15, 2026

Copy link
Copy Markdown

Summary

A user-configured CLAUDE_CODE_PATH with a leading ~ (e.g. ~/.local/bin/claude) broke memory capture entirely. The value was passed verbatim to existsSync() and then to posix_spawn via execFileSync — neither expands ~ — so the capability probe died with ENOENT ... posix_spawn '~/.local/bin/claude', findClaudeExecutable() threw, and no Claude CLI ever resolved.

The fix adds a shared expandTilde() helper and applies it:

  • defensively at read time in find-claude-executable.ts, so both the existence check and every probe spawn see a real absolute path (covers hand-edited settings files);
  • at write time in SettingsRoutes.ts, so the stored value is already resolved.

For the reported path this turns ~/.local/bin/claude back into exactly the candidate the resolver already probes via join(homedir(), '.local', 'bin', 'claude').

Why

Putting a ~ in a config path is a natural mistake, and when a user makes it the failure is total and silent — claude-mem's core observation flow stops. The only workaround was to use an absolute path.

Test plan

  • Added a find-claude-executable test asserting a tilde CLAUDE_CODE_PATH resolves to the expanded home path and that every probe spawn sees the expanded path (never the literal ~).
  • bun test tests/shared/find-claude-executable.test.ts — 21 pass.

Created with PostHog Code from an inbox report.

A user-configured CLAUDE_CODE_PATH like `~/.local/bin/claude` was passed
verbatim to existsSync and posix_spawn, neither of which expands `~`. The
capability probe died with ENOENT and findClaudeExecutable threw, silently
halting all memory capture.

Add a shared expandTilde() helper and apply it defensively at read time in
find-claude-executable.ts (so both the existence check and every probe spawn
see a real absolute path) and at write time in SettingsRoutes.ts. This turns
`~/.local/bin/claude` back into exactly the candidate the resolver already
knows how to run.

Generated-By: PostHog Code
Task-Id: ab3187bd-1feb-4ad6-9b99-9141b3117e9f
@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes Claude CLI resolution when users configure a path with a leading tilde. The main changes are:

  • Adds a shared expandTilde() helper for ~, ~/, and Windows-style ~\ prefixes.
  • Expands CLAUDE_CODE_PATH before resolver existence checks and capability probe spawns.
  • Normalizes CLAUDE_CODE_PATH when settings are saved through the worker HTTP route.
  • Adds test coverage for the reported ~/.local/bin/claude path and stabilizes a worker test mock.

Confidence Score: 5/5

Safe to merge with minimal risk.

The change is narrow and applies tilde expansion at both settings write time and resolver read time. Focused tests cover the reported path and confirm probe spawns receive the expanded path. No security concerns or runtime bugs were identified.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Ran Bun test on tests/shared/find-claude-executable.test.ts to exercise tilde path expansion; the command completed with exit code 0.
  • Inspected tests/shared/find-claude-executable.test.ts:248-256 to verify tilde path handling and the expanded spawn assertion.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src/shared/paths.ts Adds a shared expandTilde helper with injectable home directory support; no issues found.
src/shared/find-claude-executable.ts Expands configured Claude CLI paths before existence checks, capability probing, logging, caching, and returning; no issues found.
src/services/worker/http/routes/SettingsRoutes.ts Normalizes CLAUDE_CODE_PATH on settings writes by expanding a leading tilde before persisting; no issues found.
tests/shared/find-claude-executable.test.ts Adds coverage proving tilde-configured Claude paths resolve and all probe spawns receive the expanded path; no issues found.
tests/worker/claude-setup-gate.test.ts Makes the Bun module mock preserve real exports and reset the override after the suite to avoid cross-test leakage; no issues found.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant SettingsRoutes
participant SettingsFile as settings.json
participant Resolver as findClaudeExecutable
participant FS as existsSync
participant CLI as Claude CLI probe

User->>SettingsRoutes: POST /api/settings with CLAUDE_CODE_PATH
SettingsRoutes->>SettingsRoutes: expandTilde(path)
SettingsRoutes->>SettingsFile: Persist expanded absolute path
Resolver->>SettingsFile: loadSettings()
Resolver->>Resolver: expandTilde(configured path defensively)
Resolver->>FS: existsSync(expanded path)
Resolver->>CLI: execFileSync(expanded path, capability probe)
CLI-->>Resolver: version and capability result
Resolver-->>User: resolved Claude executable path
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant SettingsRoutes
participant SettingsFile as settings.json
participant Resolver as findClaudeExecutable
participant FS as existsSync
participant CLI as Claude CLI probe

User->>SettingsRoutes: POST /api/settings with CLAUDE_CODE_PATH
SettingsRoutes->>SettingsRoutes: expandTilde(path)
SettingsRoutes->>SettingsFile: Persist expanded absolute path
Resolver->>SettingsFile: loadSettings()
Resolver->>Resolver: expandTilde(configured path defensively)
Resolver->>FS: existsSync(expanded path)
Resolver->>CLI: execFileSync(expanded path, capability probe)
CLI-->>Resolver: version and capability result
Resolver-->>User: resolved Claude executable path
Loading

Reviews (2): Last reviewed commit: "test: stop claude-setup-gate's find-clau..." | Re-trigger Greptile

…iles

The CI `test` check failed on ~30 tests across find-claude-executable,
recall-mcp-server, and server-boot — none touched by this PR. Root cause:
claude-setup-gate.test.ts registered a global `mock.module` on
find-claude-executable.js that (a) exported only `findClaudeExecutable`,
dropping `_internals`/`resetClaudeExecutableCache`/`CAPABILITY_PROBE_ARGS`, and
(b) left the stub throwing after its last test. bun's mock.module is
process-global and sticky, so in CI's file order it leaked into every later
file that imports the module or transitively resolves the CLI, breaking them.

Make the mock leak-proof: snapshot and spread the real module through the
factory (preserving the other exports), default the override to the real
implementation, and restore it in afterAll so no stub escapes this file.

Generated-By: PostHog Code
Task-Id: ab3187bd-1feb-4ad6-9b99-9141b3117e9f
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.

0 participants