diff --git a/scripts/ci-hydrate-live-auth.sh b/scripts/ci-hydrate-live-auth.sh index 22e5526d029..0b2937f835a 100644 --- a/scripts/ci-hydrate-live-auth.sh +++ b/scripts/ci-hydrate-live-auth.sh @@ -28,6 +28,38 @@ write_secret_file() { chmod 600 "$destination" } +activate_claude_oauth_access_token() { + local credentials="${OPENCLAW_CLAUDE_CREDENTIALS_JSON:-}" + if [[ -z "$credentials" ]]; then + return + fi + + local access_token expires_at now_ms + local min_remaining_ms="$(( 90 * 60 * 1000 ))" + access_token="$(jq -r '.claudeAiOauth.accessToken // empty' <<<"$credentials" 2>/dev/null || true)" + expires_at="$(jq -r '.claudeAiOauth.expiresAt // 0' <<<"$credentials" 2>/dev/null || true)" + now_ms="$(( $(date +%s) * 1000 ))" + + if [[ "$access_token" != sk-ant-oat* ]]; then + echo "::warning::Claude credentials JSON has no usable OAuth access token; keeping the configured Anthropic fallback." >&2 + return + fi + # Stable live shards can run for an hour, so never shadow the fallback with + # a token that could expire before setup, retries, and cleanup complete. + if ! [[ "$expires_at" =~ ^[0-9]+$ ]] || (( expires_at <= now_ms + min_remaining_ms )); then + echo "::warning::Claude credentials JSON OAuth access token lacks 90 minutes of remaining life; keeping the configured Anthropic fallback." >&2 + return + fi + + echo "::add-mask::$access_token" + export ANTHROPIC_OAUTH_TOKEN="$access_token" + if [[ -n "${GITHUB_ENV:-}" ]]; then + printf 'ANTHROPIC_OAUTH_TOKEN=%s\n' "$access_token" >>"$GITHUB_ENV" + fi +} + +activate_claude_oauth_access_token + for env_key in \ OPENAI_API_KEY \ OPENAI_BASE_URL \ diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 21ef0038e0b..e430a8d8a51 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -1421,10 +1421,16 @@ describe("package artifact reuse", () => { ); }); - it("reuses the Claude setup-token for direct Anthropic live provider lanes", () => { + it("prefers fresh Claude OAuth credentials for direct Anthropic live provider lanes", () => { const hydrateScript = readFileSync(CI_HYDRATE_LIVE_AUTH_SCRIPT, "utf8"); expect(hydrateScript).toContain(" ANTHROPIC_OAUTH_TOKEN \\"); + expect(hydrateScript).toContain("access_token=\"$(jq -r '.claudeAiOauth.accessToken // empty'"); + expect(hydrateScript).toContain('export ANTHROPIC_OAUTH_TOKEN="$access_token"'); + expect(hydrateScript).toContain('local min_remaining_ms="$(( 90 * 60 * 1000 ))"'); + expect(hydrateScript).toContain( + 'printf \'ANTHROPIC_OAUTH_TOKEN=%s\\n\' "$access_token" >>"$GITHUB_ENV"', + ); for (const jobName of [ "validate_live_models_docker", "validate_live_models_docker_targeted",