apiKeyHelper is a shell command line, not a path, so the raw value written into settings.json was split at the first space. A Windows profile named "Mohammed Ahmed" produced an attempt to run C:\Users\Mohammed, surfacing as "your apiKeyHelper script is failing" with nothing to go on. Quote the value when it contains anything a shell cares about, and leave it bare otherwise so no existing settings.json churns on the next switch. The POSIX port had the same bug against a /Users/First Last home; shlex.quote has exactly the wanted "leave ordinary paths alone" behaviour. doctor could not see any of this. It quoted the path itself before running it, so it exercised a command line Claude Code never uses and passed while the real one failed. It now reads the string out of settings.json, reports it when it is not what a switch would write, and runs that string through a shell. The helper itself exited 1 in silence on four distinct faults - no state, no preset, no key, undecryptable key - collapsing them into one indistinguishable message. Each now names itself on stderr, which is what /status displays. The DPAPI case says what it actually means: a key stored by a different Windows account than the one Claude Code runs as. Success paths stay silent, so stdout still carries the key and nothing else. Also make install.ps1 survive a Restricted execution policy: piped through iex it is not subject to the policy, but invoking the installed script for the key prompt is, which is where a fresh install died. Set Process scope for the install, offer to set CurrentUser to RemoteSigned, and clear the mark-of-the-web that Expand-Archive can leave on the extracted scripts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
45 lines
1.8 KiB
Bash
45 lines
1.8 KiB
Bash
#!/usr/bin/env bash
|
|
# Invoked by Claude Code via the `apiKeyHelper` setting. Prints the API key for
|
|
# the active preset to stdout and nothing else.
|
|
#
|
|
# Only presets whose auth mode is "vault" have a secret to emit. In anthropic
|
|
# mode, or for a preset using an inline placeholder token (LM Studio), this exits
|
|
# silently so a token cannot leak into a context that must not have one.
|
|
|
|
set -u
|
|
|
|
# Claude Code surfaces a failing helper as "your apiKeyHelper script is failing"
|
|
# and shows this stream under /status. Exiting 1 in silence turns several
|
|
# distinct faults into one indistinguishable message, so every failure path says
|
|
# which it was. Success paths stay silent - stdout carries the key and nothing else.
|
|
fail() { printf 'claude-key-helper: %s\n' "$*" >&2; exit 1; }
|
|
|
|
CM_ROOT="${CM_ROOT:-$HOME/.claude-mode}"
|
|
# shellcheck source=/dev/null
|
|
. "$CM_ROOT/bin/cm-vault.sh"
|
|
|
|
PY="${CLAUDE_MODE_PYTHON:-python3}"
|
|
JSON="$CM_ROOT/bin/cm-json.py"
|
|
|
|
state="$CM_ROOT/state.json"
|
|
[ -f "$state" ] || exit 0
|
|
|
|
mode="$("$PY" "$JSON" get "$state" mode 2>/dev/null)"
|
|
[ "$mode" = "anthropic" ] && exit 0
|
|
[ -n "$mode" ] || exit 0
|
|
|
|
preset_name="$("$PY" "$JSON" get "$state" preset 2>/dev/null)"
|
|
[ -n "$preset_name" ] || exit 0
|
|
|
|
preset="$CM_ROOT/presets/$preset_name.json"
|
|
[ -f "$preset" ] || fail "state.json names preset '$preset_name' but $preset does not exist. Run: claude-mode presets"
|
|
|
|
auth_mode="$("$PY" "$JSON" get "$preset" auth.mode 2>/dev/null)"
|
|
[ -z "$auth_mode" ] && auth_mode="vault"
|
|
[ "$auth_mode" = "vault" ] || exit 0 # inline token: nothing for us to emit
|
|
|
|
key_ref="$("$PY" "$JSON" get "$preset" auth.keyRef 2>/dev/null)"
|
|
[ -n "$key_ref" ] || key_ref="openrouter"
|
|
|
|
cm_vault_get "$key_ref" || fail "no key readable for ref '$key_ref' from $(cm_vault_backend_label 2>/dev/null || echo 'the vault'). Run: claude-mode set-key $key_ref"
|