From 455d8d135b765fcec4edf6090903916b9c14c847 Mon Sep 17 00:00:00 2001 From: Paul O'Reilly Date: Thu, 7 May 2026 13:47:52 +1200 Subject: [PATCH] fix(z-ai): wire auth_token via apiKeyHelper, never via env MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the minimax fix to z-ai. Replaces bin/anthropic-compat-wrapper.sh (which did `exec env ANTHROPIC_AUTH_TOKEN="$(cat ...)" claude "$@"`, exposing the secret in the claude subprocess' /proc//environ) with init.sh that writes ~/.claude/settings.json with: apiKeyHelper: "cat /run/agent/secrets/z-ai/auth_token" Claude Code routes apiKeyHelper output to `Authorization: Bearer ` when ANTHROPIC_BASE_URL is non-anthropic.com — exactly what the Z.ai proxy at api.z.ai/api/anthropic requires. The legacy K8s Secret may still ship a `base_url` file; it is intentionally ignored by init.sh (the base URL is not a credential and lives in harness.yaml). Mirrors agent-runtimes commit (z-ai apiKeyHelper). CRS serves these harness files to dispatchers, so this repo must match. Co-Authored-By: Claude Opus 4.7 --- .../z-ai/v1/bin/anthropic-compat-wrapper.sh | 12 ---- harnesses/contexts/z-ai/v1/init.sh | 55 ++++++++++++++++--- 2 files changed, 46 insertions(+), 21 deletions(-) delete mode 100755 harnesses/contexts/z-ai/v1/bin/anthropic-compat-wrapper.sh diff --git a/harnesses/contexts/z-ai/v1/bin/anthropic-compat-wrapper.sh b/harnesses/contexts/z-ai/v1/bin/anthropic-compat-wrapper.sh deleted file mode 100755 index 4a6f4bc..0000000 --- a/harnesses/contexts/z-ai/v1/bin/anthropic-compat-wrapper.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -# z-ai Anthropic-compatible wrapper — injects ESO-mounted credentials -# M22 Phase 9 Wave 4 -# Files at /run/agent/secrets/z-ai/ are mounted by the dispatcher via ESO K8s Secret -set -euo pipefail - -SECRETS_DIR="/run/agent/secrets/z-ai" - -exec env \ - ANTHROPIC_AUTH_TOKEN="$(cat "$SECRETS_DIR/auth_token")" \ - ANTHROPIC_BASE_URL="$(cat "$SECRETS_DIR/base_url")" \ - claude "$@" diff --git a/harnesses/contexts/z-ai/v1/init.sh b/harnesses/contexts/z-ai/v1/init.sh index 4153594..e842327 100755 --- a/harnesses/contexts/z-ai/v1/init.sh +++ b/harnesses/contexts/z-ai/v1/init.sh @@ -1,16 +1,53 @@ #!/bin/bash -# z-ai harness init — verifies ESO secret mount exists -# M22 Phase 9 Wave 4 +# z-ai init — write ~/.claude/settings.json with apiKeyHelper. +# +# The Z.ai auth_token is mounted by ESO at +# /run/agent/secrets/z-ai/auth_token (mode 0400, secrets_required entry in +# harness.yaml). Claude Code's `apiKeyHelper` setting names a command that +# prints the key on stdout when the CLI needs it for an API request — the +# value never enters this process' environment, never appears in the claude +# subprocess' /proc//environ, and is read fresh on each invocation so +# ESO secret rotations are picked up without a process restart. +# +# When ANTHROPIC_BASE_URL points at a non-anthropic.com host (set in +# harness.yaml to https://api.z.ai/api/anthropic), Claude Code routes +# apiKeyHelper output to `Authorization: Bearer `, which is the +# header shape the Z.ai proxy requires. +# +# The K8s Secret may also contain a `base_url` file (legacy from the +# wrapper-script era) — it is intentionally ignored. The base URL is not a +# credential; it lives in harness.yaml. + set -euo pipefail -echo "=== z-ai/v1 init ===" +API_KEY_FILE="/run/agent/secrets/z-ai/auth_token" -SECRETS_DIR="/run/agent/secrets/z-ai" -if [ ! -d "$SECRETS_DIR" ]; then - echo "ERROR: ESO secret mount not found at $SECRETS_DIR" >&2 - echo "Has the dispatcher been configured with ESO-managed secrets?" >&2 +if [ ! -r "$API_KEY_FILE" ]; then + echo "ERROR: $API_KEY_FILE not readable. Check ESO ExternalSecret acct-." >&2 exit 1 fi -echo "z-ai ESO secret mount verified at $SECRETS_DIR" -echo "=== z-ai/v1 init complete ===" +CONFIG_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" +mkdir -p "$CONFIG_DIR" +chmod 0700 "$CONFIG_DIR" + +SETTINGS_FILE="$CONFIG_DIR/settings.json" + +# Merge into an existing settings.json (from another harness layer) when +# possible; otherwise create a fresh one. +if [ -f "$SETTINGS_FILE" ] && command -v jq >/dev/null 2>&1; then + TMP=$(mktemp) + jq --arg helper "cat $API_KEY_FILE" \ + '. + {apiKeyHelper: $helper}' \ + "$SETTINGS_FILE" > "$TMP" + mv "$TMP" "$SETTINGS_FILE" +else + cat > "$SETTINGS_FILE" <