Skip to content

fix(launchd): credential-proxy exits 127 when node isn't on the plist PATH - #1894

Merged
sonichi merged 2 commits into
mainfrom
jm/patch-anthropic-subscription-usage
Jul 2, 2026
Merged

sonichi merged 2 commits into
mainfrom
jm/patch-anthropic-subscription-usage

Conversation

@jsun-m

@jsun-m jsun-m commented Jul 1, 2026 •

Copy link
Copy Markdown
Collaborator

Closes

Standalone fix — no tracking issue. (Related: #1767 credential-proxy OAuth self-refresh; #1291 / #1086 launchd supervision.)

Summary

The launchd-supervised credential proxy (com.sutando.credential-proxy) exits 127 and never binds :7846 on machines where node isn't in the plist's hardcoded PATH.

credential-proxy-wrapper.sh resolves npx/tsx by absolute path, but those re-exec node via #!/usr/bin/env node, and launchd runs with only the plist's EnvironmentVariables.PATH (a fixed /opt/homebrew/bin:… guess). On an Intel-Homebrew (/usr/local/bin) / nvm / volta install, env node → No such file or directory → exit 127 → the proxy never binds :7846 (and KeepAlive's stale-holder eviction also kills any manually-started proxy).

Because the subscription core routes through this proxy (ANTHROPIC_BASE_URL=http://localhost:7846) for OAuth injection + token self-refresh (#1767), a dead proxy means every request hits ConnectionRefused — the core can't authenticate at all.

Fix: the wrapper prepends the real node install dir it finds (checks /usr/local/bin, /opt/homebrew/bin, nvm, volta) to PATH before exec, so the proxy starts regardless of the plist's guess and existing installs self-heal without re-running the installer.

Test plan

  • Repro: node at /usr/local/bin; logs/credential-proxy.log showed repeated env: node: No such file or directory, launchctl list showed last-exit 127, nothing listening on :7846, core showed API Error: Unable to connect to API (ConnectionRefused).
  • After fix: launchctl kickstart -k gui/$(id -u)/com.sutando.credential-proxy → proxy binds :7846 ([Proxy] OAuth token loaded from keychain), core reconnects, [Quota] lines resume (subscription anthropic-ratelimit-unified-* headers present), no more ConnectionRefused.
  • bash -n src/launchd/credential-proxy-wrapper.sh clean; the PATH-resolution loop verified to locate node starting from a stripped PATH=/usr/bin:/bin.

Checklist notes

  • Single concern ✅ — credential-proxy wrapper PATH only. (The subscription-clear that was originally bundled here lives in feat(agent): alternative-provider support for the Claude core (core_config) #1892, where its core-config / provider-env.sh dependencies also land.)
  • Bug confirmed on the running install (exit-127 loop) ✅
  • Author email GH-mapped ✅
  • Test: N/A — launchd/env-shell wiring, verified by the live repro above. Optional follow-up: teach install-credential-proxy-launchd.sh to write the correct node dir into the plist so fresh installs don't rely on the wrapper heal.

@jsun-m
jsun-m force-pushed the jm/patch-anthropic-subscription-usage branch 2 times, most recently from 1b2124f to 371ace8 Compare July 2, 2026 17:08
@jsun-m jsun-m changed the title fix: patch anthropic subscription usage in core cli fix(launchd): credential-proxy exits 127 when node isn't on the plist PATH Jul 2, 2026
… PATH

The launchd-supervised credential proxy (com.sutando.credential-proxy) injects
and self-refreshes (#1767) the Claude.ai subscription OAuth for the core
(ANTHROPIC_BASE_URL=http://localhost:7846). Its wrapper resolves npx/tsx by
absolute path, but those re-exec `node` via `#!/usr/bin/env node`, and launchd
runs with only the plist's fixed PATH (a /opt/homebrew/bin:… guess). On an
Intel-Homebrew (/usr/local/bin) / nvm / volta install, `env node` fails with
"No such file or directory" → the wrapper exits 127 → the proxy never binds
:7846 (and KeepAlive's stale-holder eviction kills any manual proxy). The core
then hits ConnectionRefused and can't authenticate at all.

Resolve exactly one node dir (first match wins) in the same priority order and
with the same NEWEST-nvm selection as resolve_npx/resolve_tsx: Homebrew, then
`ls ~/.nvm/versions/node | sort -V | tail -1`, then volta. Deliberately NOT a
`*/bin` glob — glob order is lexicographic, so a box with v9 + v10 would pick v9
(v10 sorts first, v9 last-wins on prepend), i.e. an older node. Prepend that one
dir before exec so the proxy starts regardless of the plist's guess and existing
installs self-heal without re-running the installer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jsun-m
jsun-m force-pushed the jm/patch-anthropic-subscription-usage branch from 371ace8 to 051da7d Compare July 2, 2026 18:19
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

@cla-assistant check

@sonichi
sonichi merged commit bd7bcef into main Jul 2, 2026
6 checks passed
@sonichi
sonichi deleted the jm/patch-anthropic-subscription-usage branch July 2, 2026 22:59
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.

2 participants