From 3ae5d95bfd815ce05ea5a14dbc6a37021cd36576 Mon Sep 17 00:00:00 2001 From: Onur Date: Thu, 16 Apr 2026 23:00:11 +0200 Subject: [PATCH] CI: fix live Docker auth mounts (#67812) * CI: fix live Docker auth mounts * CI: harden live Docker auth mounts --- scripts/test-live-acp-bind-docker.sh | 58 ++++++++++++++----- scripts/test-live-cli-backend-docker.sh | 50 ++++++++++++---- scripts/test-live-codex-harness-docker.sh | 40 ++++++++++--- scripts/test-live-gateway-models-docker.sh | 9 ++- scripts/test-live-models-docker.sh | 9 ++- src/agents/models.profiles.live.test.ts | 13 +++++ .../gateway-models.profiles.live.test.ts | 13 ++++- 7 files changed, 151 insertions(+), 41 deletions(-) diff --git a/scripts/test-live-acp-bind-docker.sh b/scripts/test-live-acp-bind-docker.sh index 5a5ab0d1960..151e6db0035 100644 --- a/scripts/test-live-acp-bind-docker.sh +++ b/scripts/test-live-acp-bind-docker.sh @@ -8,8 +8,9 @@ LIVE_IMAGE_NAME="${OPENCLAW_LIVE_IMAGE:-${IMAGE_NAME}-live}" CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}" WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$HOME/.openclaw/workspace}" PROFILE_FILE="${OPENCLAW_PROFILE_FILE:-$HOME/.profile}" -CLI_TOOLS_DIR="${OPENCLAW_DOCKER_CLI_TOOLS_DIR:-$HOME/.cache/openclaw/docker-cli-tools}" ACP_AGENT_LIST_RAW="${OPENCLAW_LIVE_ACP_BIND_AGENTS:-${OPENCLAW_LIVE_ACP_BIND_AGENT:-claude,codex,gemini}}" +TEMP_DIRS=() +DOCKER_USER="${OPENCLAW_DOCKER_USER:-node}" openclaw_live_acp_bind_resolve_auth_provider() { case "${1:-}" in @@ -32,17 +33,42 @@ openclaw_live_acp_bind_resolve_agent_command() { esac } +cleanup_temp_dirs() { + if ((${#TEMP_DIRS[@]} > 0)); then + rm -rf "${TEMP_DIRS[@]}" + fi +} +trap cleanup_temp_dirs EXIT + +if [[ -n "${OPENCLAW_DOCKER_CLI_TOOLS_DIR:-}" ]]; then + CLI_TOOLS_DIR="${OPENCLAW_DOCKER_CLI_TOOLS_DIR}" +elif [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then + CLI_TOOLS_DIR="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-docker-cli-tools.XXXXXX")" + TEMP_DIRS+=("$CLI_TOOLS_DIR") +else + CLI_TOOLS_DIR="$HOME/.cache/openclaw/docker-cli-tools" +fi + mkdir -p "$CLI_TOOLS_DIR" +if [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then + DOCKER_USER="$(id -u):$(id -g)" +fi PROFILE_MOUNT=() -if [[ -f "$PROFILE_FILE" ]]; then +if [[ -f "$PROFILE_FILE" && -r "$PROFILE_FILE" ]]; then PROFILE_MOUNT=(-v "$PROFILE_FILE":/home/node/.profile:ro) fi read -r -d '' LIVE_TEST_CMD <<'EOF' || true set -euo pipefail -[ -f "$HOME/.profile" ] && source "$HOME/.profile" || true -export PATH="$HOME/.npm-global/bin:$PATH" +[ -f "$HOME/.profile" ] && [ -r "$HOME/.profile" ] && source "$HOME/.profile" || true +export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-$HOME/.npm-global}" +export npm_config_prefix="$NPM_CONFIG_PREFIX" +export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-$HOME/.npm-cache}" +export npm_config_cache="$NPM_CONFIG_CACHE" +mkdir -p "$NPM_CONFIG_PREFIX" "$NPM_CONFIG_CACHE" +chmod 700 "$NPM_CONFIG_CACHE" || true +export PATH="$NPM_CONFIG_PREFIX/bin:$PATH" IFS=',' read -r -a auth_dirs <<<"${OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED:-}" IFS=',' read -r -a auth_files <<<"${OPENCLAW_DOCKER_AUTH_FILES_RESOLVED:-}" if ((${#auth_dirs[@]} > 0)); then @@ -68,15 +94,15 @@ fi agent="${OPENCLAW_LIVE_ACP_BIND_AGENT:-claude}" case "$agent" in claude) - if [ ! -x "$HOME/.npm-global/bin/claude" ]; then - npm_config_prefix="$HOME/.npm-global" npm install -g @anthropic-ai/claude-code + if [ ! -x "$NPM_CONFIG_PREFIX/bin/claude" ]; then + npm install -g @anthropic-ai/claude-code fi - real_claude="$HOME/.npm-global/bin/claude-real" - if [ ! -x "$real_claude" ] && [ -x "$HOME/.npm-global/bin/claude" ]; then - mv "$HOME/.npm-global/bin/claude" "$real_claude" + real_claude="$NPM_CONFIG_PREFIX/bin/claude-real" + if [ ! -x "$real_claude" ] && [ -x "$NPM_CONFIG_PREFIX/bin/claude" ]; then + mv "$NPM_CONFIG_PREFIX/bin/claude" "$real_claude" fi if [ -x "$real_claude" ]; then - cat > "$HOME/.npm-global/bin/claude" < "$NPM_CONFIG_PREFIX/bin/claude" < Auth dirs: ${AUTH_DIRS_CSV:-none}" echo "==> Auth files: ${AUTH_FILES_CSV:-none}" docker run --rm -t \ - -u node \ + -u "$DOCKER_USER" \ --entrypoint bash \ -e ANTHROPIC_API_KEY \ -e ANTHROPIC_API_KEY_OLD \ diff --git a/scripts/test-live-cli-backend-docker.sh b/scripts/test-live-cli-backend-docker.sh index 0c3c74fcf1e..13741db2318 100644 --- a/scripts/test-live-cli-backend-docker.sh +++ b/scripts/test-live-cli-backend-docker.sh @@ -8,12 +8,13 @@ LIVE_IMAGE_NAME="${OPENCLAW_LIVE_IMAGE:-${IMAGE_NAME}-live}" CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}" WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$HOME/.openclaw/workspace}" PROFILE_FILE="${OPENCLAW_PROFILE_FILE:-$HOME/.profile}" -CLI_TOOLS_DIR="${OPENCLAW_DOCKER_CLI_TOOLS_DIR:-$HOME/.cache/openclaw/docker-cli-tools}" DEFAULT_PROVIDER="${OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER:-claude-cli}" CLI_MODEL="${OPENCLAW_LIVE_CLI_BACKEND_MODEL:-}" CLI_PROVIDER="${CLI_MODEL%%/*}" CLI_DISABLE_MCP_CONFIG="${OPENCLAW_LIVE_CLI_BACKEND_DISABLE_MCP_CONFIG:-}" CLI_AUTH_MODE="${OPENCLAW_LIVE_CLI_BACKEND_AUTH:-auto}" +TEMP_DIRS=() +DOCKER_USER="${OPENCLAW_DOCKER_USER:-node}" if [[ -z "$CLI_PROVIDER" || "$CLI_PROVIDER" == "$CLI_MODEL" ]]; then CLI_PROVIDER="$DEFAULT_PROVIDER" @@ -55,7 +56,26 @@ if [[ "$CLI_PROVIDER" == "claude-cli" && -z "$CLI_DISABLE_MCP_CONFIG" ]]; then fi fi +cleanup_temp_dirs() { + if ((${#TEMP_DIRS[@]} > 0)); then + rm -rf "${TEMP_DIRS[@]}" + fi +} +trap cleanup_temp_dirs EXIT + +if [[ -n "${OPENCLAW_DOCKER_CLI_TOOLS_DIR:-}" ]]; then + CLI_TOOLS_DIR="${OPENCLAW_DOCKER_CLI_TOOLS_DIR}" +elif [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then + CLI_TOOLS_DIR="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-docker-cli-tools.XXXXXX")" + TEMP_DIRS+=("$CLI_TOOLS_DIR") +else + CLI_TOOLS_DIR="$HOME/.cache/openclaw/docker-cli-tools" +fi + mkdir -p "$CLI_TOOLS_DIR" +if [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then + DOCKER_USER="$(id -u):$(id -g)" +fi if [[ "$CLI_PROVIDER" == "claude-cli" && "$CLI_AUTH_MODE" == "subscription" ]]; then CLAUDE_CREDS_FILE="$HOME/.claude/.credentials.json" @@ -108,7 +128,7 @@ if [[ "$CLI_PROVIDER" == "claude-cli" && "$CLI_AUTH_MODE" == "subscription" ]]; fi PROFILE_MOUNT=() -if [[ -f "$PROFILE_FILE" ]]; then +if [[ -f "$PROFILE_FILE" && -r "$PROFILE_FILE" ]]; then PROFILE_MOUNT=(-v "$PROFILE_FILE":/home/node/.profile:ro) fi @@ -162,8 +182,14 @@ fi read -r -d '' LIVE_TEST_CMD <<'EOF' || true set -euo pipefail -[ -f "$HOME/.profile" ] && source "$HOME/.profile" || true -export PATH="$HOME/.npm-global/bin:$PATH" +[ -f "$HOME/.profile" ] && [ -r "$HOME/.profile" ] && source "$HOME/.profile" || true +export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-$HOME/.npm-global}" +export npm_config_prefix="$NPM_CONFIG_PREFIX" +export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-$HOME/.npm-cache}" +export npm_config_cache="$NPM_CONFIG_CACHE" +mkdir -p "$NPM_CONFIG_PREFIX" "$NPM_CONFIG_CACHE" +chmod 700 "$NPM_CONFIG_CACHE" || true +export PATH="$NPM_CONFIG_PREFIX/bin:$PATH" IFS=',' read -r -a auth_dirs <<<"${OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED:-}" IFS=',' read -r -a auth_files <<<"${OPENCLAW_DOCKER_AUTH_FILES_RESOLVED:-}" if ((${#auth_dirs[@]} > 0)); then @@ -194,10 +220,10 @@ if [ -z "$binary_name" ] && [ -n "$default_command" ]; then binary_name="$(basename "$default_command")" fi if [ -z "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ] && [ -n "$binary_name" ]; then - export OPENCLAW_LIVE_CLI_BACKEND_COMMAND="$HOME/.npm-global/bin/$binary_name" + export OPENCLAW_LIVE_CLI_BACKEND_COMMAND="$NPM_CONFIG_PREFIX/bin/$binary_name" fi if [ -n "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ] && [ ! -x "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" ] && [ -n "$docker_package" ]; then - npm_config_prefix="$HOME/.npm-global" npm install -g "$docker_package" + npm install -g "$docker_package" fi if [ "$provider" = "claude-cli" ]; then auth_mode="${OPENCLAW_LIVE_CLI_BACKEND_AUTH:-auto}" @@ -224,12 +250,12 @@ if (fs.existsSync(file)) { } NODE fi - real_claude="$HOME/.npm-global/bin/claude-real" - if [ ! -x "$real_claude" ] && [ -x "$HOME/.npm-global/bin/claude" ]; then - mv "$HOME/.npm-global/bin/claude" "$real_claude" + real_claude="$NPM_CONFIG_PREFIX/bin/claude-real" + if [ ! -x "$real_claude" ] && [ -x "$NPM_CONFIG_PREFIX/bin/claude" ]; then + mv "$NPM_CONFIG_PREFIX/bin/claude" "$real_claude" fi if [ -x "$real_claude" ]; then - cat > "$HOME/.npm-global/bin/claude" < "$NPM_CONFIG_PREFIX/bin/claude" < 0)); then + rm -rf "${TEMP_DIRS[@]}" + fi +} +trap cleanup_temp_dirs EXIT + +if [[ -n "${OPENCLAW_DOCKER_CLI_TOOLS_DIR:-}" ]]; then + CLI_TOOLS_DIR="${OPENCLAW_DOCKER_CLI_TOOLS_DIR}" +elif [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then + CLI_TOOLS_DIR="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-docker-cli-tools.XXXXXX")" + TEMP_DIRS+=("$CLI_TOOLS_DIR") +else + CLI_TOOLS_DIR="$HOME/.cache/openclaw/docker-cli-tools" +fi mkdir -p "$CLI_TOOLS_DIR" +if [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then + DOCKER_USER="$(id -u):$(id -g)" +fi PROFILE_MOUNT=() -if [[ -f "$PROFILE_FILE" ]]; then +if [[ -f "$PROFILE_FILE" && -r "$PROFILE_FILE" ]]; then PROFILE_MOUNT=(-v "$PROFILE_FILE":/home/node/.profile:ro) fi @@ -40,8 +60,14 @@ fi read -r -d '' LIVE_TEST_CMD <<'EOF' || true set -euo pipefail -[ -f "$HOME/.profile" ] && source "$HOME/.profile" || true -export PATH="$HOME/.npm-global/bin:$PATH" +[ -f "$HOME/.profile" ] && [ -r "$HOME/.profile" ] && source "$HOME/.profile" || true +export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-$HOME/.npm-global}" +export npm_config_prefix="$NPM_CONFIG_PREFIX" +export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-$HOME/.npm-cache}" +export npm_config_cache="$NPM_CONFIG_CACHE" +mkdir -p "$NPM_CONFIG_PREFIX" "$NPM_CONFIG_CACHE" +chmod 700 "$NPM_CONFIG_CACHE" || true +export PATH="$NPM_CONFIG_PREFIX/bin:$PATH" IFS=',' read -r -a auth_files <<<"${OPENCLAW_DOCKER_AUTH_FILES_RESOLVED:-}" if ((${#auth_files[@]} > 0)); then for auth_file in "${auth_files[@]}"; do @@ -53,8 +79,8 @@ if ((${#auth_files[@]} > 0)); then fi done fi -if [ ! -x "$HOME/.npm-global/bin/codex" ]; then - npm_config_prefix="$HOME/.npm-global" npm install -g @openai/codex +if [ ! -x "$NPM_CONFIG_PREFIX/bin/codex" ]; then + npm install -g @openai/codex fi tmp_dir="$(mktemp -d)" cleanup() { @@ -83,7 +109,7 @@ echo "==> MCP probe: ${OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE:-1}" echo "==> Harness fallback: none" echo "==> Auth files: ${AUTH_FILES_CSV:-none}" docker run --rm -t \ - -u node \ + -u "$DOCKER_USER" \ --entrypoint bash \ -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ -e HOME=/home/node \ diff --git a/scripts/test-live-gateway-models-docker.sh b/scripts/test-live-gateway-models-docker.sh index 64748f8f17a..09339f93f4a 100755 --- a/scripts/test-live-gateway-models-docker.sh +++ b/scripts/test-live-gateway-models-docker.sh @@ -8,9 +8,13 @@ LIVE_IMAGE_NAME="${OPENCLAW_LIVE_IMAGE:-${IMAGE_NAME}-live}" CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}" WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$HOME/.openclaw/workspace}" PROFILE_FILE="${OPENCLAW_PROFILE_FILE:-$HOME/.profile}" +DOCKER_USER="${OPENCLAW_DOCKER_USER:-node}" +if [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then + DOCKER_USER="$(id -u):$(id -g)" +fi PROFILE_MOUNT=() -if [[ -f "$PROFILE_FILE" ]]; then +if [[ -f "$PROFILE_FILE" && -r "$PROFILE_FILE" ]]; then PROFILE_MOUNT=(-v "$PROFILE_FILE":/home/node/.profile:ro) fi @@ -73,7 +77,7 @@ fi read -r -d '' LIVE_TEST_CMD <<'EOF' || true set -euo pipefail -[ -f "$HOME/.profile" ] && source "$HOME/.profile" || true +[ -f "$HOME/.profile" ] && [ -r "$HOME/.profile" ] && source "$HOME/.profile" || true IFS=',' read -r -a auth_dirs <<<"${OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED:-}" IFS=',' read -r -a auth_files <<<"${OPENCLAW_DOCKER_AUTH_FILES_RESOLVED:-}" if ((${#auth_dirs[@]} > 0)); then @@ -117,6 +121,7 @@ echo "==> Target: src/gateway/gateway-models.profiles.live.test.ts" echo "==> External auth dirs: ${AUTH_DIRS_CSV:-none}" echo "==> External auth files: ${AUTH_FILES_CSV:-none}" docker run --rm -t \ + -u "$DOCKER_USER" \ --entrypoint bash \ -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ -e HOME=/home/node \ diff --git a/scripts/test-live-models-docker.sh b/scripts/test-live-models-docker.sh index 09f85e8080c..47728f643df 100755 --- a/scripts/test-live-models-docker.sh +++ b/scripts/test-live-models-docker.sh @@ -6,6 +6,7 @@ source "$ROOT_DIR/scripts/lib/live-docker-auth.sh" IMAGE_NAME="${OPENCLAW_IMAGE:-openclaw:local}" LIVE_IMAGE_NAME="${OPENCLAW_LIVE_IMAGE:-${IMAGE_NAME}-live}" PROFILE_FILE="${OPENCLAW_PROFILE_FILE:-$HOME/.profile}" +DOCKER_USER="${OPENCLAW_DOCKER_USER:-node}" openclaw_live_truthy() { case "${1:-}" in @@ -35,9 +36,12 @@ else CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}" WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$HOME/.openclaw/workspace}" fi +if [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then + DOCKER_USER="$(id -u):$(id -g)" +fi PROFILE_MOUNT=() -if [[ -f "$PROFILE_FILE" ]]; then +if [[ -f "$PROFILE_FILE" && -r "$PROFILE_FILE" ]]; then PROFILE_MOUNT=(-v "$PROFILE_FILE":/home/node/.profile:ro) fi @@ -110,7 +114,7 @@ fi read -r -d '' LIVE_TEST_CMD <<'EOF' || true set -euo pipefail -[ -f "$HOME/.profile" ] && source "$HOME/.profile" || true +[ -f "$HOME/.profile" ] && [ -r "$HOME/.profile" ] && source "$HOME/.profile" || true IFS=',' read -r -a auth_dirs <<<"${OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED:-}" IFS=',' read -r -a auth_files <<<"${OPENCLAW_DOCKER_AUTH_FILES_RESOLVED:-}" if ((${#auth_dirs[@]} > 0)); then @@ -155,6 +159,7 @@ echo "==> Profile env only: ${OPENCLAW_DOCKER_PROFILE_ENV_ONLY:-0}" echo "==> External auth dirs: ${AUTH_DIRS_CSV:-none}" echo "==> External auth files: ${AUTH_FILES_CSV:-none}" docker run --rm -t \ + -u "$DOCKER_USER" \ --entrypoint bash \ -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ -e HOME=/home/node \ diff --git a/src/agents/models.profiles.live.test.ts b/src/agents/models.profiles.live.test.ts index 968ab86deb0..f65527b6e24 100644 --- a/src/agents/models.profiles.live.test.ts +++ b/src/agents/models.profiles.live.test.ts @@ -192,6 +192,10 @@ function isRefreshTokenReused(raw: string): boolean { return /refresh_token_reused/i.test(raw); } +function isAccountIdExtractionError(raw: string): boolean { + return /failed to extract accountid from token/i.test(raw); +} + function isInstructionsRequiredError(raw: string): boolean { return /instructions are required/i.test(raw); } @@ -794,6 +798,15 @@ describeLive("live models (profile keys)", () => { logProgress(`${progressLabel}: skip (codex refresh token reused)`); break; } + if ( + allowNotFoundSkip && + model.provider === "openai-codex" && + isAccountIdExtractionError(message) + ) { + skipped.push({ model: id, reason: message }); + logProgress(`${progressLabel}: skip (codex account id extraction)`); + break; + } if ( allowNotFoundSkip && model.provider === "openai-codex" && diff --git a/src/gateway/gateway-models.profiles.live.test.ts b/src/gateway/gateway-models.profiles.live.test.ts index 52b0e12fcc0..e3a748a3e6b 100644 --- a/src/gateway/gateway-models.profiles.live.test.ts +++ b/src/gateway/gateway-models.profiles.live.test.ts @@ -548,6 +548,10 @@ function isRefreshTokenReused(error: string): boolean { return /refresh_token_reused/i.test(error); } +function isAccountIdExtractionError(error: string): boolean { + return /failed to extract accountid from token/i.test(error); +} + function isChatGPTUsageLimitErrorMessage(raw: string): boolean { const msg = raw.toLowerCase(); return msg.includes("hit your chatgpt usage limit") && msg.includes("try again in"); @@ -675,10 +679,10 @@ describe("getHighSignalLiveModelPriorityIndex", () => { it("prefers curated Google replacements over big-pickle", () => { expect( getHighSignalLiveModelPriorityIndex({ provider: "google", id: "gemini-3.1-pro-preview" }), - ).toBe(2); + ).toBe(3); expect( getHighSignalLiveModelPriorityIndex({ provider: "google", id: "gemini-3-flash-preview" }), - ).toBe(3); + ).toBe(4); expect(getHighSignalLiveModelPriorityIndex({ provider: "opencode", id: "big-pickle" })).toBe( null, ); @@ -1926,6 +1930,11 @@ async function runGatewayModelSuite(params: GatewayModelSuiteParams) { logProgress(`${progressLabel}: skip (codex refresh token reused)`); break; } + if (model.provider === "openai-codex" && isAccountIdExtractionError(message)) { + skippedCount += 1; + logProgress(`${progressLabel}: skip (codex account id extraction)`); + break; + } if (model.provider === "openai-codex" && isChatGPTUsageLimitErrorMessage(message)) { skippedCount += 1; logProgress(`${progressLabel}: skip (chatgpt usage limit)`);