name: OpenClaw Live And E2E Checks (Reusable) on: workflow_dispatch: inputs: ref: description: Ref, tag, or SHA to validate required: true default: main type: string include_repo_e2e: description: Whether to run pnpm test:e2e plus repo-specific extra E2E lanes required: false default: true type: boolean include_release_path_suites: description: Whether to run the Docker release-path suites required: false default: true type: boolean include_openwebui: description: Whether to run the Open WebUI Docker smoke required: false default: true type: boolean docker_lanes: description: Comma/space separated Docker scheduler lane names to run against the prepared image required: false default: "" type: string targeted_docker_lane_group_size: description: Number of targeted Docker lanes to batch into one runner job required: false default: 1 type: number published_upgrade_survivor_baseline: description: Published OpenClaw package baseline for the published-upgrade-survivor/update-migration Docker lanes required: false default: openclaw@latest type: string published_upgrade_survivor_baselines: description: Optional exact baseline list for published-upgrade-survivor/update-migration lane expansion required: false default: "" type: string published_upgrade_survivor_scenarios: description: Optional scenario list for published-upgrade-survivor/update-migration lane expansion required: false default: "" type: string package_artifact_name: description: Existing workflow artifact containing openclaw-current.tgz; blank packs the selected ref required: false default: "" type: string package_artifact_run_id: description: Prior run id containing package_artifact_name; blank uses this run or packs the selected ref required: false default: "" type: string docker_e2e_bare_image: description: Existing bare Docker E2E image to reuse; blank derives from package SHA/ref required: false default: "" type: string docker_e2e_functional_image: description: Existing functional Docker E2E image to reuse; blank derives from package SHA/ref required: false default: "" type: string codex_plugin_spec: description: Optional Codex plugin install spec for the live package lane; blank packs extensions/codex from the selected ref required: false default: "" type: string include_live_suites: description: Whether to run live-provider coverage required: false default: true type: boolean live_models_only: description: Whether to run only the Docker live model matrix when live suites are enabled required: false default: false type: boolean live_model_providers: description: Comma/space separated provider ids for the Docker live model matrix; blank runs all providers required: false default: "" type: string live_suite_filter: description: Optional exact live suite id to run for focused failed-shard recovery; blank runs all selected suites required: false default: "" type: string release_test_profile: description: Release coverage profile for live/Docker/provider breadth required: false default: stable type: choice options: - beta - stable - full use_github_hosted_runners: description: Use GitHub-hosted runners instead of Blacksmith runners required: false default: false type: boolean advisory: description: Treat failures as advisory for the caller required: false default: false type: boolean workflow_call: inputs: advisory: description: Treat failures as advisory for the caller required: false default: false type: boolean ref: description: Ref, tag, or SHA to validate required: true type: string include_repo_e2e: description: Whether to run pnpm test:e2e required: false default: false type: boolean include_release_path_suites: description: Whether to run the Docker release-path suites required: false default: false type: boolean include_openwebui: description: Whether to run the Open WebUI Docker smoke required: false default: true type: boolean docker_lanes: description: Comma/space separated Docker scheduler lane names to run against the prepared image required: false default: "" type: string targeted_docker_lane_group_size: description: Number of targeted Docker lanes to batch into one runner job required: false default: 1 type: number published_upgrade_survivor_baseline: description: Published OpenClaw package baseline for the published-upgrade-survivor/update-restart-auth/update-migration Docker lanes required: false default: openclaw@latest type: string published_upgrade_survivor_baselines: description: Optional exact baseline list for published-upgrade-survivor/update-migration lane expansion required: false default: "" type: string published_upgrade_survivor_scenarios: description: Optional scenario list for published-upgrade-survivor/update-migration lane expansion required: false default: "" type: string package_artifact_name: description: Existing workflow artifact containing openclaw-current.tgz; blank packs the selected ref required: false default: "" type: string package_artifact_run_id: description: Prior run id containing package_artifact_name; blank uses this run or packs the selected ref required: false default: "" type: string docker_e2e_bare_image: description: Existing bare Docker E2E image to reuse; blank derives from package SHA/ref required: false default: "" type: string docker_e2e_functional_image: description: Existing functional Docker E2E image to reuse; blank derives from package SHA/ref required: false default: "" type: string codex_plugin_spec: description: Optional Codex plugin install spec for the live package lane; blank packs extensions/codex from the selected ref required: false default: "" type: string include_live_suites: description: Whether to run live-provider coverage required: false default: true type: boolean live_models_only: description: Whether to run only the Docker live model matrix when live suites are enabled required: false default: false type: boolean live_model_providers: description: Comma/space separated provider ids for the Docker live model matrix; blank runs all providers required: false default: "" type: string live_suite_filter: description: Optional exact live suite id to run for focused failed-shard recovery; blank runs all selected suites required: false default: "" type: string release_test_profile: description: Release coverage profile for live/Docker/provider breadth required: false default: stable type: string use_github_hosted_runners: description: Use GitHub-hosted runners instead of Blacksmith runners required: false default: true type: boolean secrets: OPENAI_API_KEY: required: false OPENAI_BASE_URL: required: false ANTHROPIC_API_KEY: required: false ANTHROPIC_API_KEY_OLD: required: false ANTHROPIC_API_TOKEN: required: false FACTORY_API_KEY: required: false BYTEPLUS_API_KEY: required: false CEREBRAS_API_KEY: required: false DEEPINFRA_API_KEY: required: false DASHSCOPE_API_KEY: required: false GROQ_API_KEY: required: false KIMI_API_KEY: required: false MODELSTUDIO_API_KEY: required: false MOONSHOT_API_KEY: required: false MISTRAL_API_KEY: required: false MINIMAX_API_KEY: required: false OPENCODE_API_KEY: required: false OPENCODE_ZEN_API_KEY: required: false OPENCLAW_LIVE_BROWSER_CDP_URL: required: false OPENCLAW_LIVE_SETUP_TOKEN: required: false OPENCLAW_LIVE_SETUP_TOKEN_MODEL: required: false OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: required: false OPENCLAW_LIVE_SETUP_TOKEN_VALUE: required: false GEMINI_API_KEY: required: false GOOGLE_API_KEY: required: false OPENROUTER_API_KEY: required: false QWEN_API_KEY: required: false FAL_KEY: required: false RUNWAY_API_KEY: required: false DEEPGRAM_API_KEY: required: false TOGETHER_API_KEY: required: false VYDRA_API_KEY: required: false XAI_API_KEY: required: false ZAI_API_KEY: required: false Z_AI_API_KEY: required: false BYTEPLUS_ACCESS_KEY_ID: required: false BYTEPLUS_SECRET_ACCESS_KEY: required: false CLAUDE_CODE_OAUTH_TOKEN: required: false OPENCLAW_CODEX_AUTH_JSON: required: false OPENCLAW_CODEX_CONFIG_TOML: required: false OPENCLAW_CLAUDE_JSON: required: false OPENCLAW_CLAUDE_CREDENTIALS_JSON: required: false OPENCLAW_CLAUDE_SETTINGS_JSON: required: false OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: required: false OPENCLAW_GEMINI_SETTINGS_JSON: required: false FIREWORKS_API_KEY: required: false permissions: contents: read packages: write pull-requests: read env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" NODE_VERSION: "24.15.0" jobs: validate_selected_ref: runs-on: ubuntu-24.04 outputs: selected_sha: ${{ steps.validate.outputs.selected_sha }} trusted_reason: ${{ steps.validate.outputs.trusted_reason }} steps: - name: Checkout workflow repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 0 - name: Validate selected ref id: validate env: INPUT_REF: ${{ inputs.ref }} shell: bash run: | set -euo pipefail trusted_reason="" # Resolve here instead of in actions/checkout so short SHAs work too. if ! selected_sha="$(git rev-parse --verify "${INPUT_REF}^{commit}")"; then echo "Ref '${INPUT_REF}' could not be resolved to a commit." >&2 exit 1 fi if git merge-base --is-ancestor "$selected_sha" refs/remotes/origin/main; then trusted_reason="main-ancestor" elif git tag --points-at "$selected_sha" | grep -Eq '^v'; then trusted_reason="release-tag" elif git for-each-ref --format='%(refname:short)' --contains "$selected_sha" refs/remotes/origin | grep -Eq '^origin/'; then trusted_reason="repository-branch-history" else trusted_reason="" fi if [[ -z "$trusted_reason" ]]; then echo "Ref '${INPUT_REF}' resolved to $selected_sha, which is not trusted for secret-bearing live/E2E checks." >&2 echo "Allowed refs must be reachable from an OpenClaw branch or release tag." >&2 exit 1 fi echo "selected_sha=$selected_sha" >> "$GITHUB_OUTPUT" echo "trusted_reason=$trusted_reason" >> "$GITHUB_OUTPUT" { echo "Validated ref: \`${INPUT_REF}\`" echo "Resolved SHA: \`$selected_sha\`" echo "Trust reason: \`$trusted_reason\`" } >> "$GITHUB_STEP_SUMMARY" validate_live_suite_filter: runs-on: ubuntu-24.04 if: inputs.live_suite_filter != '' steps: - name: Validate focused live suite filter shell: bash env: LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }} RELEASE_TEST_PROFILE: ${{ inputs.release_test_profile }} INCLUDE_REPO_E2E: ${{ inputs.include_repo_e2e }} INCLUDE_LIVE_SUITES: ${{ inputs.include_live_suites }} LIVE_MODELS_ONLY: ${{ inputs.live_models_only }} LIVE_MODEL_PROVIDERS: ${{ inputs.live_model_providers }} run: | set -euo pipefail selected_suites=() add_suite() { selected_suites+=("$1") } add_profile_suite() { local suite_id="$1" local profiles="$2" if [[ " ${profiles} " == *" ${RELEASE_TEST_PROFILE} "* ]]; then add_suite "$suite_id" fi } live_model_providers="${LIVE_MODEL_PROVIDERS//,/}" live_model_providers="${live_model_providers//[[:space:]]/}" if [[ "$INCLUDE_REPO_E2E" == "true" ]]; then add_suite openshell-e2e fi if [[ "$INCLUDE_LIVE_SUITES" == "true" ]]; then if [[ -n "$live_model_providers" ]]; then add_suite docker-live-models else add_profile_suite docker-live-models "beta minimum stable full" fi if [[ "$LIVE_MODELS_ONLY" != "true" ]]; then add_suite live-cache add_profile_suite native-live-src-agents "stable full" add_profile_suite native-live-src-agents-zai-coding "stable full" add_profile_suite native-live-src-gateway-core "beta minimum stable full" add_profile_suite native-live-src-gateway-profiles-anthropic "stable full" add_profile_suite native-live-src-gateway-profiles-anthropic-smoke "stable" add_profile_suite native-live-src-gateway-profiles-anthropic-opus "full" add_profile_suite native-live-src-gateway-profiles-anthropic-sonnet-haiku "full" add_profile_suite native-live-src-gateway-profiles-google "stable full" add_profile_suite native-live-src-gateway-profiles-minimax "stable full" add_profile_suite native-live-src-gateway-profiles-openai "beta minimum stable full" add_profile_suite native-live-src-gateway-profiles-fireworks "full" add_profile_suite native-live-src-gateway-profiles-deepseek "full" add_profile_suite native-live-src-gateway-profiles-opencode-go "full" add_profile_suite native-live-src-gateway-profiles-opencode-go-smoke "stable" add_profile_suite native-live-src-gateway-profiles-openrouter "full" add_profile_suite native-live-src-gateway-profiles-xai "full" add_profile_suite native-live-src-gateway-profiles-zai "full" add_profile_suite native-live-src-gateway-backends "stable full" add_profile_suite native-live-src-infra "stable full" add_profile_suite native-live-test "stable full" add_profile_suite native-live-extensions-l-n "full" add_profile_suite native-live-extensions-moonshot "full" add_profile_suite native-live-extensions-openai "beta minimum stable full" add_profile_suite native-live-extensions-o-z-other "full" add_profile_suite native-live-extensions-xai "full" add_profile_suite live-gateway-docker "beta minimum stable full" add_profile_suite live-gateway-anthropic-docker "stable full" add_profile_suite live-gateway-google-docker "stable full" add_profile_suite live-gateway-minimax-docker "stable full" add_profile_suite live-gateway-advisory-docker "full" add_profile_suite live-gateway-advisory-docker-deepseek-fireworks "full" add_profile_suite live-gateway-advisory-docker-opencode-openrouter "full" add_profile_suite live-gateway-advisory-docker-xai-zai "full" add_profile_suite live-cli-backend-docker "stable full" add_profile_suite live-acp-bind-docker "stable full" add_profile_suite live-codex-harness-docker "stable full" add_profile_suite live-subagent-announce-docker "stable full" add_profile_suite native-live-extensions-a-k "full" add_profile_suite native-live-extensions-media-audio "full" add_profile_suite native-live-extensions-media-music-google "full" add_profile_suite native-live-extensions-media-music-minimax "full" add_profile_suite native-live-extensions-media-video "full" fi fi for suite_id in "${selected_suites[@]}"; do if [[ "$LIVE_SUITE_FILTER" == "$suite_id" ]]; then echo "Focused live suite filter is valid: ${LIVE_SUITE_FILTER}" exit 0 fi done echo "live_suite_filter '${LIVE_SUITE_FILTER}' does not match any runnable suite for release_test_profile='${RELEASE_TEST_PROFILE}' and the selected include flags." >&2 if [[ "${#selected_suites[@]}" -gt 0 ]]; then printf 'Runnable focused suite ids: %s\n' "${selected_suites[*]}" >&2 else echo "No focused live suites are runnable for the selected include flags." >&2 fi exit 1 plan_release_workflow_matrices: needs: validate_selected_ref runs-on: ubuntu-24.04 outputs: docker_e2e_count: ${{ steps.plan.outputs.docker_e2e_count }} docker_e2e_matrix: ${{ steps.plan.outputs.docker_e2e_matrix }} docker_e2e_omitted_json: ${{ steps.plan.outputs.docker_e2e_omitted_json }} live_models_count: ${{ steps.plan.outputs.live_models_count }} live_models_matrix: ${{ steps.plan.outputs.live_models_matrix }} live_models_omitted_json: ${{ steps.plan.outputs.live_models_omitted_json }} steps: - name: Checkout trusted release harness uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} fetch-depth: 1 - name: Plan release workflow matrices id: plan env: DOCKER_LANES: ${{ inputs.docker_lanes }} INCLUDE_LIVE_SUITES: ${{ inputs.include_live_suites }} INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include_release_path_suites }} LIVE_MODEL_PROVIDERS: ${{ inputs.live_model_providers }} LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }} RELEASE_TEST_PROFILE: ${{ inputs.release_test_profile }} run: node scripts/plan-release-workflow-matrix.mjs >> "$GITHUB_OUTPUT" validate_release_live_cache: needs: validate_selected_ref if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'live-cache') continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }} timeout-minutes: 20 env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} OPENCLAW_LIVE_CACHE_TEST: "1" OPENCLAW_LIVE_TEST: "1" steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Setup Node environment uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Validate live cache credentials run: | set -euo pipefail if [[ -z "${OPENAI_API_KEY:-}" ]]; then echo "Missing OPENAI_API_KEY secret for live-cache validation." >&2 exit 1 fi if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then echo "Missing ANTHROPIC_API_KEY secret for live-cache validation." >&2 exit 1 fi - name: Verify live prompt cache floors run: | set -euo pipefail for attempt in 1 2; do echo "live-cache attempt ${attempt}/2" if timeout --foreground --kill-after=30s 8m pnpm test:live:cache; then exit 0 fi if [[ "$attempt" == "2" ]]; then exit 1 fi sleep $((attempt * 15)) done validate_repo_e2e: needs: validate_selected_ref if: inputs.include_repo_e2e && inputs.live_suite_filter == '' continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }} timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }} env: OPENCLAW_VITEST_MAX_WORKERS: "2" steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Setup Node environment uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Build dist for repo E2E env: NODE_OPTIONS: --max-old-space-size=8192 run: pnpm build - name: Install Playwright Chromium run: pnpm --dir ui exec playwright install --with-deps chromium - name: Run repo E2E suite run: pnpm test:e2e validate_special_e2e: needs: validate_selected_ref if: inputs.include_repo_e2e && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'openshell-e2e') continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }} timeout-minutes: ${{ matrix.timeout_minutes }} strategy: fail-fast: false matrix: include: - suite_id: openshell-e2e label: OpenShell repo E2E command: pnpm test:e2e:openshell timeout_minutes: 60 requires_repo_e2e: true requires_live_suites: false env: OPENCLAW_E2E_WORKERS: "1" OPENCLAW_VITEST_MAX_WORKERS: "1" steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Setup Node environment uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Build dist for special E2E if: | ( (inputs.include_repo_e2e && matrix.requires_repo_e2e) || (inputs.include_live_suites && matrix.requires_live_suites) ) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id) env: NODE_OPTIONS: --max-old-space-size=8192 run: pnpm build - name: Configure suite-specific env if: inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id shell: bash run: | set -euo pipefail case "${{ matrix.suite_id }}" in openshell-e2e) echo "OPENCLAW_E2E_OPENSHELL_CONFIG_HOME=$HOME/.config" >> "$GITHUB_ENV" ;; esac - name: Install OpenShell CLI if: | (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id) && matrix.suite_id == 'openshell-e2e' shell: bash run: | set -euo pipefail export OPENSHELL_VERSION=v0.0.68 curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/d64542f69d06694cbd203b64929d286dd0533bbb/install.sh | sh openshell --version - name: Bootstrap OpenShell gateway if: | (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id) && matrix.suite_id == 'openshell-e2e' shell: bash run: | set -euo pipefail mtls_dir="$HOME/.config/openshell/gateways/openshell/mtls" gateway_tls_dir="$RUNNER_TEMP/openshell-gateway-certs" fallback_pid="" if ! openshell --gateway openshell sandbox list >/dev/null 2>&1; then rm -rf "$gateway_tls_dir" openshell-gateway generate-certs \ --output-dir "$gateway_tls_dir" \ --server-san 127.0.0.1 \ --server-san localhost \ --server-san host.openshell.internal rm -rf "$mtls_dir" mkdir -p "$mtls_dir" cp "$gateway_tls_dir/ca.crt" "$mtls_dir/ca.crt" cp "$gateway_tls_dir/client/tls.crt" "$mtls_dir/tls.crt" cp "$gateway_tls_dir/client/tls.key" "$mtls_dir/tls.key" openshell gateway remove openshell >/dev/null 2>&1 || true OPENSHELL_LOCAL_TLS_DIR="$gateway_tls_dir" nohup openshell-gateway \ --bind-address 0.0.0.0 \ --port 17670 \ --drivers docker \ --tls-cert "$gateway_tls_dir/server/tls.crt" \ --tls-key "$gateway_tls_dir/server/tls.key" \ --tls-client-ca "$mtls_dir/ca.crt" \ >"$RUNNER_TEMP/openshell-gateway.log" 2>&1 & fallback_pid=$! echo "OPENCLAW_OPENSHELL_FALLBACK_PID=$fallback_pid" >> "$GITHUB_ENV" for _ in $(seq 1 30); do if openshell gateway add --local --name openshell https://127.0.0.1:17670; then break fi sleep 1 done openshell gateway select openshell for _ in $(seq 1 60); do if openshell --gateway openshell sandbox list >/dev/null 2>&1; then break fi sleep 1 done fi if [[ -z "$fallback_pid" ]]; then echo "OPENCLAW_OPENSHELL_FALLBACK_PID=" >> "$GITHUB_ENV" fi openshell --gateway openshell sandbox list >/dev/null openshell gateway list - name: Validate suite credentials if: inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id shell: bash run: | set -euo pipefail case "${{ matrix.suite_id }}" in openshell-e2e) ;; esac - name: Run ${{ matrix.label }} if: | ( (inputs.include_repo_e2e && matrix.requires_repo_e2e) || (inputs.include_live_suites && matrix.requires_live_suites) ) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id) run: ${{ matrix.command }} - name: Stop fallback OpenShell gateway if: always() && matrix.suite_id == 'openshell-e2e' shell: bash run: | set -euo pipefail if [[ -n "${OPENCLAW_OPENSHELL_FALLBACK_PID:-}" ]]; then kill "$OPENCLAW_OPENSHELL_FALLBACK_PID" 2>/dev/null || true fi validate_docker_e2e: needs: [validate_selected_ref, prepare_docker_e2e_image, plan_release_workflow_matrices] if: inputs.include_release_path_suites && inputs.docker_lanes == '' && needs.plan_release_workflow_matrices.outputs.docker_e2e_count != '0' name: Docker E2E (${{ matrix.label }}) continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }} timeout-minutes: ${{ matrix.timeout_minutes }} strategy: fail-fast: false matrix: ${{ fromJson(needs.plan_release_workflow_matrices.outputs.docker_e2e_matrix) }} env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }} ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }} FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }} BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }} CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }} DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }} DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }} KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }} MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }} OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }} OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }} OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }} OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }} OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }} FAL_KEY: ${{ secrets.FAL_KEY }} RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }} DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }} BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }} BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }} OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }} OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }} OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }} OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }} OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }} OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }} FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }} OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }} OPENCLAW_DOCKER_E2E_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }} OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }} OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }} OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }} OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }} OPENCLAW_DOCKER_ALL_RELEASE_PROFILE: ${{ inputs.release_test_profile }} OPENCLAW_CODEX_NPM_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }} OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }} OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }} OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }} OPENCLAW_SKIP_DOCKER_BUILD: "1" INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }} DOCKER_E2E_CHUNK: ${{ matrix.chunk_id }} steps: - name: Checkout selected ref if: contains(matrix.profiles, inputs.release_test_profile) uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Checkout trusted release harness if: contains(matrix.profiles, inputs.release_test_profile) uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} fetch-depth: 1 path: .release-harness - name: Log in to GHCR for shared Docker E2E image if: contains(matrix.profiles, inputs.release_test_profile) run: bash .release-harness/scripts/ci-docker-login-ghcr.sh env: GHCR_USERNAME: ${{ github.actor }} GITHUB_TOKEN: ${{ github.token }} - name: Setup Node environment if: contains(matrix.profiles, inputs.release_test_profile) uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Hydrate live auth/profile inputs if: contains(matrix.profiles, inputs.release_test_profile) run: bash scripts/ci-hydrate-live-auth.sh - name: Plan Docker E2E chunk if: contains(matrix.profiles, inputs.release_test_profile) id: plan shell: bash env: CHUNK: ${{ matrix.chunk_id }} INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }} RELEASE_TEST_PROFILE: ${{ inputs.release_test_profile }} run: | set -euo pipefail if [[ -z "$CHUNK" ]]; then echo "chunk input is required for Docker E2E chunk planning." >&2 exit 1 fi mkdir -p .artifacts/docker-tests export OPENCLAW_DOCKER_ALL_PROFILE=release-path export OPENCLAW_DOCKER_ALL_CHUNK="$CHUNK" export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="$INCLUDE_OPENWEBUI" export OPENCLAW_DOCKER_ALL_RELEASE_PROFILE="$RELEASE_TEST_PROFILE" plan_path=".artifacts/docker-tests/release-${CHUNK}-plan.json" node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path" node .release-harness/scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT" echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT" - name: Download OpenClaw Docker E2E package if: contains(matrix.profiles, inputs.release_test_profile) && steps.plan.outputs.needs_package == '1' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }} path: .artifacts/docker-e2e-package - name: Pull shared bare Docker E2E image if: contains(matrix.profiles, inputs.release_test_profile) && steps.plan.outputs.needs_bare_image == '1' shell: bash run: | set -euo pipefail bash .release-harness/scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_BARE_IMAGE}" - name: Pull shared functional Docker E2E image if: contains(matrix.profiles, inputs.release_test_profile) && steps.plan.outputs.needs_functional_image == '1' shell: bash run: | set -euo pipefail bash .release-harness/scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}" - name: Validate Docker E2E credentials if: contains(matrix.profiles, inputs.release_test_profile) shell: bash env: CREDENTIALS: ${{ steps.plan.outputs.credentials }} run: | set -euo pipefail credentials=",$CREDENTIALS," require_any() { local label="$1" shift local key for key in "$@"; do if [[ -n "${!key:-}" ]]; then return 0 fi done echo "Missing credential for ${label}: expected one of $*" >&2 exit 1 } if [[ "$credentials" == *",openai,"* ]]; then require_any OpenAI OPENAI_API_KEY fi if [[ "$credentials" == *",codex,"* ]]; then require_any Codex OPENCLAW_CODEX_AUTH_JSON fi if [[ "$credentials" == *",anthropic,"* ]]; then require_any Anthropic ANTHROPIC_API_TOKEN ANTHROPIC_API_KEY OPENCLAW_CLAUDE_CREDENTIALS_JSON OPENCLAW_CLAUDE_JSON fi if [[ "$credentials" == *",factory,"* ]]; then require_any Factory FACTORY_API_KEY fi if [[ "$credentials" == *",gemini,"* ]]; then require_any Gemini GEMINI_API_KEY GOOGLE_API_KEY OPENCLAW_GEMINI_SETTINGS_JSON fi if [[ "$credentials" == *",opencode,"* ]]; then require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY fi - name: Run Docker E2E chunk if: contains(matrix.profiles, inputs.release_test_profile) shell: bash run: | set -euo pipefail export OPENCLAW_DOCKER_ALL_PROFILE=release-path export OPENCLAW_DOCKER_ALL_CHUNK="${DOCKER_E2E_CHUNK}" export OPENCLAW_DOCKER_ALL_RELEASE_PROFILE="${OPENCLAW_DOCKER_ALL_RELEASE_PROFILE}" export OPENCLAW_DOCKER_ALL_BUILD=0 export OPENCLAW_DOCKER_ALL_PREFLIGHT=0 export OPENCLAW_DOCKER_ALL_FAIL_FAST=0 export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="${INCLUDE_OPENWEBUI}" export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}" export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}-timings.json" export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)" if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh fi node .release-harness/scripts/test-docker-all.mjs - name: Summarize Docker E2E chunk if: always() shell: bash run: | set -euo pipefail summary=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}/summary.json" if [[ ! -f "$summary" ]]; then echo "Docker chunk summary missing: \`$summary\`" >> "$GITHUB_STEP_SUMMARY" exit 1 fi node .release-harness/scripts/docker-e2e.mjs summary "$summary" "Docker E2E chunk: ${DOCKER_E2E_CHUNK:-unknown}" >> "$GITHUB_STEP_SUMMARY" - name: Upload Docker E2E chunk artifacts if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: docker-e2e-${{ matrix.chunk_id }} path: .artifacts/docker-tests/ if-no-files-found: error plan_docker_lane_groups: needs: validate_selected_ref if: inputs.docker_lanes != '' continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }} timeout-minutes: 5 outputs: groups_json: ${{ steps.groups.outputs.groups_json }} steps: - name: Checkout trusted release harness uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} fetch-depth: 1 - name: Build targeted Docker lane groups id: groups shell: bash env: LANES: ${{ inputs.docker_lanes }} GROUP_SIZE: ${{ inputs.targeted_docker_lane_group_size }} OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }} run: | set -euo pipefail groups_json="$(node scripts/plan-targeted-docker-lane-groups.mjs)" echo "groups_json=${groups_json}" >> "$GITHUB_OUTPUT" validate_docker_lanes: needs: [validate_selected_ref, prepare_docker_e2e_image, plan_docker_lane_groups] if: inputs.docker_lanes != '' name: Docker E2E targeted lanes (${{ matrix.group.label }}) continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }} timeout-minutes: 60 strategy: fail-fast: false matrix: group: ${{ fromJson(needs.plan_docker_lane_groups.outputs.groups_json) }} env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }} ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }} FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }} BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }} CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }} DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }} DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }} KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }} MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }} OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }} OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }} OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }} OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }} OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }} FAL_KEY: ${{ secrets.FAL_KEY }} RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }} DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }} BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }} BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }} OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }} OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }} OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }} OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }} OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }} OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }} FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }} OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }} OPENCLAW_DOCKER_E2E_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }} OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }} OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }} OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }} OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }} OPENCLAW_CODEX_NPM_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }} OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }} OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ matrix.group.published_upgrade_survivor_baselines || inputs.published_upgrade_survivor_baselines }} OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }} OPENCLAW_SKIP_DOCKER_BUILD: "1" INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }} DOCKER_E2E_LANES: ${{ matrix.group.docker_lanes }} steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Checkout trusted release harness uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false ref: ${{ github.sha }} fetch-depth: 1 path: .release-harness - name: Log in to GHCR for shared Docker E2E image run: bash .release-harness/scripts/ci-docker-login-ghcr.sh env: GHCR_USERNAME: ${{ github.actor }} GITHUB_TOKEN: ${{ github.token }} - name: Setup Node environment uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Hydrate live auth/profile inputs run: bash scripts/ci-hydrate-live-auth.sh - name: Plan targeted Docker E2E lanes id: plan shell: bash env: LANES: ${{ matrix.group.docker_lanes }} GROUP_LABEL: ${{ matrix.group.label }} INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }} INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include_release_path_suites }} run: | set -euo pipefail if [[ -z "$LANES" ]]; then echo "lanes input is required for Docker E2E targeted planning." >&2 exit 1 fi mkdir -p .artifacts/docker-tests export OPENCLAW_DOCKER_ALL_LANES="$LANES" export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="$INCLUDE_OPENWEBUI" if [[ "$INCLUDE_RELEASE_PATH_SUITES" == "true" ]]; then export OPENCLAW_DOCKER_ALL_PROFILE=release-path fi plan_path=".artifacts/docker-tests/targeted-plan.json" node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path" node .release-harness/scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT" suffix="$(printf '%s' "${GROUP_LABEL:-$LANES}" | tr ',[:space:]' '-' | tr -cd 'A-Za-z0-9._-' | sed -E 's/-+/-/g; s/^-//; s/-$//')" echo "artifact_suffix=${suffix:-targeted}" >> "$GITHUB_OUTPUT" echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT" - name: Download OpenClaw Docker E2E package if: steps.plan.outputs.needs_package == '1' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }} path: .artifacts/docker-e2e-package - name: Pull shared bare Docker E2E image if: steps.plan.outputs.needs_bare_image == '1' shell: bash run: | set -euo pipefail bash .release-harness/scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_BARE_IMAGE}" - name: Pull shared functional Docker E2E image if: steps.plan.outputs.needs_functional_image == '1' shell: bash run: | set -euo pipefail bash .release-harness/scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}" - name: Validate Docker E2E credentials shell: bash env: CREDENTIALS: ${{ steps.plan.outputs.credentials }} run: | set -euo pipefail credentials=",$CREDENTIALS," require_any() { local label="$1" shift local key for key in "$@"; do if [[ -n "${!key:-}" ]]; then return 0 fi done echo "Missing credential for ${label}: expected one of $*" >&2 exit 1 } if [[ "$credentials" == *",openai,"* ]]; then require_any OpenAI OPENAI_API_KEY fi if [[ "$credentials" == *",codex,"* ]]; then require_any Codex OPENCLAW_CODEX_AUTH_JSON fi if [[ "$credentials" == *",anthropic,"* ]]; then require_any Anthropic ANTHROPIC_API_TOKEN ANTHROPIC_API_KEY OPENCLAW_CLAUDE_CREDENTIALS_JSON OPENCLAW_CLAUDE_JSON fi if [[ "$credentials" == *",factory,"* ]]; then require_any Factory FACTORY_API_KEY fi if [[ "$credentials" == *",gemini,"* ]]; then require_any Gemini GEMINI_API_KEY GOOGLE_API_KEY OPENCLAW_GEMINI_SETTINGS_JSON fi if [[ "$credentials" == *",opencode,"* ]]; then require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY fi - name: Run targeted Docker E2E lanes shell: bash run: | set -euo pipefail export OPENCLAW_DOCKER_ALL_LANES="${DOCKER_E2E_LANES}" export OPENCLAW_DOCKER_ALL_PREFLIGHT=0 export OPENCLAW_DOCKER_ALL_FAIL_FAST=0 export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="${INCLUDE_OPENWEBUI}" if [[ "${{ inputs.include_release_path_suites }}" == "true" ]]; then export OPENCLAW_DOCKER_ALL_PROFILE=release-path fi export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/targeted-${{ steps.plan.outputs.artifact_suffix }}" export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/targeted-${{ steps.plan.outputs.artifact_suffix }}-timings.json" export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)" if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh fi export OPENCLAW_DOCKER_ALL_BUILD=0 node .release-harness/scripts/test-docker-all.mjs - name: Summarize targeted Docker E2E lanes if: always() shell: bash run: | set -euo pipefail summary=".artifacts/docker-tests/targeted-${{ steps.plan.outputs.artifact_suffix }}/summary.json" if [[ ! -f "$summary" ]]; then echo "Docker targeted summary missing: \`$summary\`" >> "$GITHUB_STEP_SUMMARY" exit 1 fi node .release-harness/scripts/docker-e2e.mjs summary "$summary" "Docker E2E targeted lanes" >> "$GITHUB_STEP_SUMMARY" - name: Upload targeted Docker E2E artifacts if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: docker-e2e-${{ steps.plan.outputs.artifact_suffix }} path: .artifacts/docker-tests/ if-no-files-found: error validate_docker_openwebui: needs: [validate_selected_ref, prepare_docker_e2e_image] if: inputs.include_openwebui && !inputs.include_release_path_suites && inputs.docker_lanes == '' name: Docker E2E (openwebui) continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }} timeout-minutes: 60 env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }} OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }} OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }} OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }} OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }} OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz OPENCLAW_SKIP_DOCKER_BUILD: "1" steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Checkout trusted release harness uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.sha }} fetch-depth: 1 path: .release-harness - name: Log in to GHCR for shared Docker E2E image run: bash .release-harness/scripts/ci-docker-login-ghcr.sh env: GHCR_USERNAME: ${{ github.actor }} GITHUB_TOKEN: ${{ github.token }} - name: Setup Node environment uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Validate Open WebUI credentials shell: bash run: | set -euo pipefail [[ -n "${OPENAI_API_KEY:-}" ]] || { echo "OPENAI_API_KEY is required for the Open WebUI Docker smoke." >&2 exit 1 } - name: Plan Open WebUI Docker E2E chunk id: plan shell: bash run: | set -euo pipefail mkdir -p .artifacts/docker-tests export OPENCLAW_DOCKER_ALL_PROFILE=release-path export OPENCLAW_DOCKER_ALL_CHUNK=openwebui export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI=true plan_path=".artifacts/docker-tests/release-openwebui-plan.json" node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path" node .release-harness/scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT" echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT" - name: Download OpenClaw Docker E2E package if: steps.plan.outputs.needs_package == '1' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }} path: .artifacts/docker-e2e-package - name: Pull shared bare Docker E2E image if: steps.plan.outputs.needs_bare_image == '1' shell: bash run: | set -euo pipefail bash .release-harness/scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_BARE_IMAGE}" - name: Pull shared functional Docker E2E image if: steps.plan.outputs.needs_functional_image == '1' shell: bash run: | set -euo pipefail bash .release-harness/scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}" - name: Run Open WebUI Docker E2E chunk shell: bash run: | set -euo pipefail export OPENCLAW_DOCKER_ALL_PROFILE=release-path export OPENCLAW_DOCKER_ALL_CHUNK=openwebui export OPENCLAW_DOCKER_ALL_BUILD=0 export OPENCLAW_DOCKER_ALL_PREFLIGHT=0 export OPENCLAW_DOCKER_ALL_FAIL_FAST=0 export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI=1 export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/release-openwebui" export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/release-openwebui-timings.json" export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)" if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh fi node .release-harness/scripts/test-docker-all.mjs - name: Summarize Open WebUI Docker E2E chunk if: always() shell: bash run: | set -euo pipefail summary=".artifacts/docker-tests/release-openwebui/summary.json" if [[ ! -f "$summary" ]]; then echo "Docker Open WebUI summary missing: \`$summary\`" >> "$GITHUB_STEP_SUMMARY" exit 1 fi node .release-harness/scripts/docker-e2e.mjs summary "$summary" "Docker E2E chunk: openwebui" >> "$GITHUB_STEP_SUMMARY" - name: Upload Open WebUI Docker E2E artifacts if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: docker-e2e-openwebui path: .artifacts/docker-tests/ if-no-files-found: error prepare_docker_e2e_image: needs: validate_selected_ref if: inputs.include_release_path_suites || inputs.include_openwebui || inputs.docker_lanes != '' continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }} timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }} permissions: actions: read contents: read packages: write outputs: image: ${{ steps.image.outputs.image }} bare_image: ${{ steps.image.outputs.bare_image }} functional_image: ${{ steps.image.outputs.functional_image }} needs_bare_image: ${{ steps.plan.outputs.needs_bare_image }} needs_e2e_image: ${{ steps.plan.outputs.needs_e2e_image }} needs_functional_image: ${{ steps.plan.outputs.needs_functional_image }} needs_live_image: ${{ steps.plan.outputs.needs_live_image }} needs_package: ${{ steps.plan.outputs.needs_package }} env: DOCKER_BUILD_SUMMARY: "false" DOCKER_BUILD_RECORD_UPLOAD: "false" OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }} steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Checkout trusted release harness uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.sha }} fetch-depth: 1 path: .release-harness - name: Plan Docker E2E images id: plan shell: bash env: LANES: ${{ inputs.docker_lanes }} INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include_release_path_suites }} INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }} RELEASE_TEST_PROFILE: ${{ inputs.release_test_profile }} OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }} OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }} OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }} run: | set -euo pipefail mkdir -p .artifacts/docker-tests if [[ "$INCLUDE_RELEASE_PATH_SUITES" == "true" ]]; then export OPENCLAW_DOCKER_ALL_PROFILE=release-path export OPENCLAW_DOCKER_ALL_PLAN_RELEASE_ALL=1 elif [[ -n "$LANES" ]]; then export OPENCLAW_DOCKER_ALL_LANES="$LANES" elif [[ "$INCLUDE_OPENWEBUI" == "true" ]]; then export OPENCLAW_DOCKER_ALL_LANES=openwebui fi export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="$INCLUDE_OPENWEBUI" export OPENCLAW_DOCKER_ALL_RELEASE_PROFILE="$RELEASE_TEST_PROFILE" plan_path=".artifacts/docker-tests/plan.json" node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path" node .release-harness/scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT" echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT" - name: Setup Node environment if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_name == '' && inputs.package_artifact_run_id == '' uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Download current-run OpenClaw Docker E2E package if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_name != '' && inputs.package_artifact_run_id == '' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.package_artifact_name }} path: .artifacts/docker-e2e-package - name: Download previous-run OpenClaw Docker E2E package if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_run_id != '' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }} path: .artifacts/docker-e2e-package run-id: ${{ inputs.package_artifact_run_id }} github-token: ${{ github.token }} - name: Pack OpenClaw package for Docker E2E if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_name == '' && inputs.package_artifact_run_id == '' shell: bash run: | set -euo pipefail mkdir -p .artifacts/docker-e2e-package node scripts/package-openclaw-for-docker.mjs \ --output-dir .artifacts/docker-e2e-package \ --output-name openclaw-current.tgz - name: Validate OpenClaw Docker E2E package id: package if: steps.plan.outputs.needs_package == '1' shell: bash run: | set -euo pipefail mkdir -p .artifacts/docker-e2e-package target=".artifacts/docker-e2e-package/openclaw-current.tgz" if [[ ! -f "$target" ]]; then mapfile -t tgzs < <(find .artifacts/docker-e2e-package -type f -name '*.tgz' | sort) if [[ "${#tgzs[@]}" -ne 1 ]]; then echo "Expected exactly one package tarball in .artifacts/docker-e2e-package; found ${#tgzs[@]}." >&2 printf '%s\n' "${tgzs[@]}" >&2 exit 1 fi cp "${tgzs[0]}" "$target" fi echo "Validating Docker E2E package tarball: $target" started_at="$(date +%s)" timeout --foreground 5m node scripts/check-openclaw-package-tarball.mjs "$target" finished_at="$(date +%s)" echo "Docker E2E package tarball validation finished in $((finished_at - started_at))s." digest="$(sha256sum "$target" | awk '{print $1}')" tag="pkg-${digest:0:32}" echo "sha256=$digest" >> "$GITHUB_OUTPUT" echo "tag=$tag" >> "$GITHUB_OUTPUT" { echo "Docker E2E package: \`$target\`" echo "Docker E2E package SHA-256: \`$digest\`" } >> "$GITHUB_STEP_SUMMARY" - name: Upload OpenClaw Docker E2E package if: steps.plan.outputs.needs_package == '1' && (inputs.package_artifact_name == '' || inputs.package_artifact_run_id != '') uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }} path: .artifacts/docker-e2e-package/openclaw-current.tgz if-no-files-found: error - name: Resolve shared Docker E2E image tags id: image shell: bash env: PACKAGE_TAG: ${{ steps.package.outputs.tag }} SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }} PROVIDED_BARE_IMAGE: ${{ inputs.docker_e2e_bare_image }} PROVIDED_FUNCTIONAL_IMAGE: ${{ inputs.docker_e2e_functional_image }} run: | set -euo pipefail repository="${GITHUB_REPOSITORY,,}" image_tag="${PACKAGE_TAG:-$SELECTED_SHA}" bare_image="${PROVIDED_BARE_IMAGE:-ghcr.io/${repository}-docker-e2e-bare:${image_tag}}" functional_image="${PROVIDED_FUNCTIONAL_IMAGE:-ghcr.io/${repository}-docker-e2e-functional:${image_tag}}" image="$functional_image" echo "image=$image" >> "$GITHUB_OUTPUT" echo "bare_image=$bare_image" >> "$GITHUB_OUTPUT" echo "functional_image=$functional_image" >> "$GITHUB_OUTPUT" echo "Shared Docker E2E bare image: \`$bare_image\`" >> "$GITHUB_STEP_SUMMARY" echo "Shared Docker E2E functional image: \`$functional_image\`" >> "$GITHUB_STEP_SUMMARY" - name: Log in to GHCR if: steps.plan.outputs.needs_e2e_image == '1' run: bash .release-harness/scripts/ci-docker-login-ghcr.sh env: GHCR_USERNAME: ${{ github.actor }} GITHUB_TOKEN: ${{ github.token }} - name: Check existing shared Docker E2E images id: image_exists if: steps.plan.outputs.needs_e2e_image == '1' shell: bash env: PROVIDED_BARE_IMAGE: ${{ inputs.docker_e2e_bare_image }} PROVIDED_FUNCTIONAL_IMAGE: ${{ inputs.docker_e2e_functional_image }} run: | set -euo pipefail bare_exists=0 functional_exists=0 needs_build=0 if [[ "${{ steps.plan.outputs.needs_bare_image }}" == "1" ]]; then if docker manifest inspect "${{ steps.image.outputs.bare_image }}" >/dev/null 2>&1; then bare_exists=1 echo "Shared Docker E2E bare image already exists: ${{ steps.image.outputs.bare_image }}" elif [[ -n "$PROVIDED_BARE_IMAGE" ]]; then echo "Provided bare Docker E2E image does not exist: $PROVIDED_BARE_IMAGE" >&2 exit 1 else needs_build=1 fi fi if [[ "${{ steps.plan.outputs.needs_functional_image }}" == "1" ]]; then if docker manifest inspect "${{ steps.image.outputs.functional_image }}" >/dev/null 2>&1; then functional_exists=1 echo "Shared Docker E2E functional image already exists: ${{ steps.image.outputs.functional_image }}" elif [[ -n "$PROVIDED_FUNCTIONAL_IMAGE" ]]; then echo "Provided functional Docker E2E image does not exist: $PROVIDED_FUNCTIONAL_IMAGE" >&2 exit 1 else needs_build=1 fi fi echo "bare_exists=$bare_exists" >> "$GITHUB_OUTPUT" echo "functional_exists=$functional_exists" >> "$GITHUB_OUTPUT" echo "needs_build=$needs_build" >> "$GITHUB_OUTPUT" - name: Setup Docker builder if: steps.image_exists.outputs.needs_build == '1' uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1 with: max-cache-size-mb: 800000 - name: Build and push bare Docker E2E image if: steps.plan.outputs.needs_bare_image == '1' && steps.image_exists.outputs.bare_exists != '1' shell: bash env: IMAGE_REF: ${{ steps.image.outputs.bare_image }} run: | set -euo pipefail build_cmd=( docker buildx build --file ./scripts/e2e/Dockerfile --target bare --platform linux/amd64 --tag "$IMAGE_REF" --sbom=true --provenance=mode=max --push . ) for attempt in 1 2 3 4; do if "${build_cmd[@]}"; then exit 0 fi if [[ "$attempt" == "4" ]]; then echo "::error::Failed to build Docker E2E bare image after ${attempt} attempts" exit 1 fi sleep_seconds=$((attempt * 20)) echo "Docker E2E bare image build failed; retrying in ${sleep_seconds}s (${attempt}/4)." sleep "$sleep_seconds" done - name: Build and push functional Docker E2E image if: steps.plan.outputs.needs_functional_image == '1' && steps.image_exists.outputs.functional_exists != '1' shell: bash env: IMAGE_REF: ${{ steps.image.outputs.functional_image }} run: | set -euo pipefail build_cmd=( docker buildx build --file ./scripts/e2e/Dockerfile --target functional --build-context openclaw_package=.artifacts/docker-e2e-package --platform linux/amd64 --tag "$IMAGE_REF" --sbom=true --provenance=mode=max --push . ) for attempt in 1 2 3 4; do if "${build_cmd[@]}"; then exit 0 fi if [[ "$attempt" == "4" ]]; then echo "::error::Failed to build Docker E2E functional image after ${attempt} attempts" exit 1 fi sleep_seconds=$((attempt * 20)) echo "Docker E2E functional image build failed; retrying in ${sleep_seconds}s (${attempt}/4)." sleep "$sleep_seconds" done prepare_live_test_image: needs: validate_selected_ref if: inputs.include_live_suites && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-') || startsWith(inputs.live_suite_filter, 'docker-live-models')) continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }} timeout-minutes: 60 permissions: contents: read packages: write outputs: live_image: ${{ steps.image.outputs.live_image }} env: DOCKER_BUILD_SUMMARY: "false" DOCKER_BUILD_RECORD_UPLOAD: "false" steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Resolve shared live-test image tag id: image shell: bash env: SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }} run: | set -euo pipefail repository="${GITHUB_REPOSITORY,,}" live_image_extensions="matrix,acpx" live_image_tag_suffix="${live_image_extensions//,/-}" live_image="ghcr.io/${repository}-live-test:${SELECTED_SHA}-${live_image_tag_suffix}" echo "live_image=${live_image}" >> "$GITHUB_OUTPUT" echo "live_image_extensions=${live_image_extensions}" >> "$GITHUB_OUTPUT" echo "Shared live-test image: \`${live_image}\`" >> "$GITHUB_STEP_SUMMARY" - name: Log in to GHCR run: bash scripts/ci-docker-login-ghcr.sh env: GHCR_USERNAME: ${{ github.actor }} GITHUB_TOKEN: ${{ github.token }} - name: Check existing shared live-test image id: image_exists shell: bash run: | set -euo pipefail if docker manifest inspect "${{ steps.image.outputs.live_image }}" >/dev/null 2>&1; then echo "Shared live-test image already exists: ${{ steps.image.outputs.live_image }}" echo "exists=1" >> "$GITHUB_OUTPUT" else echo "exists=0" >> "$GITHUB_OUTPUT" fi - name: Setup Docker builder if: steps.image_exists.outputs.exists != '1' uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1 with: max-cache-size-mb: 800000 - name: Build and push shared live-test image if: steps.image_exists.outputs.exists != '1' uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2 with: context: . file: ./Dockerfile target: build build-args: | OPENCLAW_EXTENSIONS=${{ steps.image.outputs.live_image_extensions }} platforms: linux/amd64 tags: ${{ steps.image.outputs.live_image }} sbom: true provenance: mode=max push: true validate_live_models_docker: name: Docker live models (${{ matrix.provider_label }}) needs: [validate_selected_ref, prepare_live_test_image, plan_release_workflow_matrices] if: inputs.include_live_suites && inputs.live_model_providers == '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models') && needs.plan_release_workflow_matrices.outputs.live_models_count != '0' continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }} timeout-minutes: 45 strategy: fail-fast: false matrix: ${{ fromJson(needs.plan_release_workflow_matrices.outputs.live_models_matrix) }} env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }} ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }} BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }} CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }} DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }} DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }} KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }} MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }} OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }} OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }} OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }} OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }} OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }} OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }} FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }} OPENCLAW_LIVE_PROVIDERS: ${{ matrix.providers }} OPENCLAW_LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }} OPENCLAW_LIVE_MODELS: ${{ matrix.models || 'modern' }} OPENCLAW_LIVE_MAX_MODELS: ${{ matrix.max_models || '6' }} OPENCLAW_LIVE_MODEL_TIMEOUT_MS: "45000" OPENCLAW_SKIP_DOCKER_BUILD: "1" OPENCLAW_VITEST_MAX_WORKERS: "2" steps: - name: Checkout selected ref if: contains(matrix.profiles, inputs.release_test_profile) uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Checkout trusted live Docker harness if: contains(matrix.profiles, inputs.release_test_profile) uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.sha }} fetch-depth: 1 path: .release-harness - name: Setup Node environment if: contains(matrix.profiles, inputs.release_test_profile) uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Hydrate live auth/profile inputs if: contains(matrix.profiles, inputs.release_test_profile) run: bash scripts/ci-hydrate-live-auth.sh - name: Log in to GHCR if: contains(matrix.profiles, inputs.release_test_profile) run: bash .release-harness/scripts/ci-docker-login-ghcr.sh env: GHCR_USERNAME: ${{ github.actor }} GITHUB_TOKEN: ${{ github.token }} - name: Validate provider credential if: contains(matrix.profiles, inputs.release_test_profile) shell: bash env: LIVE_MODEL_PROVIDERS: ${{ matrix.providers }} run: | set -euo pipefail require_any() { local label="$1" shift local key for key in "$@"; do if [[ -n "${!key:-}" ]]; then return 0 fi done echo "Missing credential for ${label}: expected one of $*" >&2 exit 1 } case "${LIVE_MODEL_PROVIDERS}" in anthropic) require_any Anthropic ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;; google) require_any Google GEMINI_API_KEY GOOGLE_API_KEY ;; minimax) require_any MiniMax MINIMAX_API_KEY ;; moonshot) require_any Moonshot MOONSHOT_API_KEY KIMI_API_KEY ;; openai) require_any OpenAI OPENAI_API_KEY ;; opencode-go) require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY ;; openrouter) require_any OpenRouter OPENROUTER_API_KEY ;; xai) require_any xAI XAI_API_KEY ;; zai) require_any Z.ai ZAI_API_KEY Z_AI_API_KEY ;; fireworks) require_any Fireworks FIREWORKS_API_KEY ;; *) echo "Unhandled live model provider shard: ${LIVE_MODEL_PROVIDERS}" >&2 exit 1 ;; esac - name: Run Docker live model sweep if: contains(matrix.profiles, inputs.release_test_profile) run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh validate_live_models_docker_targeted: name: Docker live models (selected providers) needs: [validate_selected_ref, prepare_live_test_image] if: inputs.include_live_suites && inputs.live_model_providers != '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models') continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }} timeout-minutes: 45 env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }} ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }} BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }} CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }} DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }} DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }} KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }} MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }} OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }} OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }} OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }} OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }} OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }} OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }} FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }} REQUESTED_LIVE_MODEL_PROVIDERS: ${{ inputs.live_model_providers }} OPENCLAW_LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }} OPENCLAW_LIVE_MAX_MODELS: "6" OPENCLAW_LIVE_MODEL_TIMEOUT_MS: "45000" OPENCLAW_SKIP_DOCKER_BUILD: "1" OPENCLAW_VITEST_MAX_WORKERS: "2" steps: - name: Checkout selected ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Checkout trusted live Docker harness uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.sha }} fetch-depth: 1 path: .release-harness - name: Setup Node environment uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Normalize provider allowlist shell: bash run: | set -euo pipefail all_providers=(anthropic google minimax moonshot openai opencode-go openrouter xai zai fireworks) normalize_provider() { local value="${1,,}" case "$value" in opencode|opencode-go) echo "opencode-go" ;; open-router|openrouter) echo "openrouter" ;; *) echo "$value" ;; esac } is_known_provider() { local value="$1" local provider for provider in "${all_providers[@]}"; do [[ "$provider" == "$value" ]] && return 0 done return 1 } selected=() declare -A seen=() raw="${REQUESTED_LIVE_MODEL_PROVIDERS:-}" normalized_all="${raw,,}" normalized_all="${normalized_all//[[:space:],]/}" if [[ -z "$normalized_all" || "$normalized_all" == "all" ]]; then selected=("${all_providers[@]}") else while IFS= read -r entry; do [[ -z "$entry" ]] && continue provider="$(normalize_provider "$entry")" if ! is_known_provider "$provider"; then echo "Unknown live model provider '${entry}'. Expected one of: ${all_providers[*]}" >&2 exit 1 fi if [[ -z "${seen[$provider]:-}" ]]; then selected+=("$provider") seen[$provider]=1 fi done < <(printf '%s\n' "$raw" | tr ',' '\n' | tr '[:space:]' '\n') fi if [[ "${#selected[@]}" -eq 0 ]]; then echo "No live model providers selected." >&2 exit 1 fi providers_csv="$(IFS=,; echo "${selected[*]}")" echo "OPENCLAW_LIVE_PROVIDERS=$providers_csv" >> "$GITHUB_ENV" { echo "Live model providers: \`$providers_csv\`" } >> "$GITHUB_STEP_SUMMARY" - name: Hydrate live auth/profile inputs run: bash scripts/ci-hydrate-live-auth.sh - name: Log in to GHCR run: bash .release-harness/scripts/ci-docker-login-ghcr.sh env: GHCR_USERNAME: ${{ github.actor }} GITHUB_TOKEN: ${{ github.token }} - name: Validate provider credentials shell: bash run: | set -euo pipefail require_any() { local label="$1" shift local key for key in "$@"; do if [[ -n "${!key:-}" ]]; then return 0 fi done echo "Missing credential for ${label}: expected one of $*" >&2 exit 1 } IFS=',' read -r -a providers <<<"${OPENCLAW_LIVE_PROVIDERS}" for provider in "${providers[@]}"; do case "$provider" in anthropic) require_any Anthropic ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;; google) require_any Google GEMINI_API_KEY GOOGLE_API_KEY ;; minimax) require_any MiniMax MINIMAX_API_KEY ;; moonshot) require_any Moonshot MOONSHOT_API_KEY KIMI_API_KEY ;; openai) require_any OpenAI OPENAI_API_KEY ;; opencode-go) require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY ;; openrouter) require_any OpenRouter OPENROUTER_API_KEY ;; xai) require_any xAI XAI_API_KEY ;; zai) require_any Z.ai ZAI_API_KEY Z_AI_API_KEY ;; fireworks) require_any Fireworks FIREWORKS_API_KEY ;; *) echo "Unhandled live model provider shard: ${provider}" >&2 exit 1 ;; esac done - name: Run Docker live model sweep run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh validate_live_provider_suites: needs: validate_selected_ref if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || (startsWith(inputs.live_suite_filter, 'native-live-') && !startsWith(inputs.live_suite_filter, 'native-live-extensions-media') && inputs.live_suite_filter != 'native-live-extensions-a-k')) continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }} timeout-minutes: ${{ matrix.timeout_minutes }} strategy: fail-fast: false matrix: include: - suite_id: native-live-src-agents label: Native live agents command: node .release-harness/scripts/test-live-shard.mjs native-live-src-agents timeout_minutes: 60 profile_env_only: false profiles: stable full - suite_id: native-live-src-agents-zai-coding label: Native live Z.AI Coding Plan command: ZAI_CODING_LIVE_TEST=1 node .release-harness/scripts/test-live-shard.mjs native-live-src-agents-zai-coding timeout_minutes: 15 profile_env_only: false profiles: stable full - suite_id: native-live-src-gateway-core label: Native live gateway core command: OPENCLAW_LIVE_CODEX_HARNESS=1 OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-core timeout_minutes: 60 profile_env_only: false profiles: beta minimum stable full - suite_id: native-live-src-gateway-profiles-anthropic-smoke suite_group: native-live-src-gateway-profiles-anthropic label: Native live gateway profiles Anthropic smoke command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-sonnet-4-6,anthropic/claude-haiku-4-5 OPENCLAW_LIVE_GATEWAY_SMOKE=1 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 45 profile_env_only: false profiles: stable - suite_id: native-live-src-gateway-profiles-anthropic-opus suite_group: native-live-src-gateway-profiles-anthropic label: Native live gateway profiles Anthropic Opus command: OPENCLAW_LIVE_GATEWAY_THINKING=low OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-opus-4-8 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-profiles-anthropic-sonnet-haiku suite_group: native-live-src-gateway-profiles-anthropic label: Native live gateway profiles Anthropic Sonnet/Haiku command: OPENCLAW_LIVE_GATEWAY_THINKING=low OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-sonnet-4-6,anthropic/claude-haiku-4-5 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-profiles-google label: Native live gateway profiles Google command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=google OPENCLAW_LIVE_GATEWAY_MODELS=google/gemini-3.1-pro-preview node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 60 profile_env_only: false profiles: stable full - suite_id: native-live-src-gateway-profiles-minimax label: Native live gateway profiles MiniMax command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 60 profile_env_only: false profiles: stable full - suite_id: native-live-src-gateway-profiles-openai label: Native live gateway profiles OpenAI command: OPENCLAW_LIVE_GATEWAY_THINKING=off OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.5 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=180000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=600000 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 60 profile_env_only: false profiles: beta minimum stable full - suite_id: native-live-src-gateway-profiles-fireworks label: Native live gateway profiles Fireworks command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=fireworks node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-profiles-deepseek label: Native live gateway profiles DeepSeek command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-profiles-opencode-go-deepseek-glm suite_group: native-live-src-gateway-profiles-opencode-go label: Native live gateway profiles OpenCode Go DeepSeek/GLM command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go OPENCLAW_LIVE_GATEWAY_MODELS=opencode-go/deepseek-v4-flash,opencode-go/deepseek-v4-pro,opencode-go/glm-5,opencode-go/glm-5.1 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-profiles-opencode-go-kimi suite_group: native-live-src-gateway-profiles-opencode-go label: Native live gateway profiles OpenCode Go Kimi command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go OPENCLAW_LIVE_GATEWAY_MODELS=opencode-go/kimi-k2.5,opencode-go/kimi-k2.6 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-profiles-opencode-go-mimo suite_group: native-live-src-gateway-profiles-opencode-go label: Native live gateway profiles OpenCode Go MiMo command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go OPENCLAW_LIVE_GATEWAY_MODELS=opencode-go/mimo-v2-omni,opencode-go/mimo-v2-pro,opencode-go/mimo-v2.5,opencode-go/mimo-v2.5-pro node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-profiles-opencode-go-minimax-qwen suite_group: native-live-src-gateway-profiles-opencode-go label: Native live gateway profiles OpenCode Go MiniMax/Qwen command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go OPENCLAW_LIVE_GATEWAY_MODELS=opencode-go/minimax-m2.5,opencode-go/minimax-m2.7,opencode-go/qwen3.5-plus,opencode-go/qwen3.6-plus node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-profiles-opencode-go-smoke label: Native live gateway profiles OpenCode Go smoke command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go OPENCLAW_LIVE_GATEWAY_SMOKE=1 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 45 profile_env_only: false profiles: stable - suite_id: native-live-src-gateway-profiles-openrouter label: Native live gateway profiles OpenRouter command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openrouter node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-profiles-xai label: Native live gateway profiles xAI command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-profiles-zai label: Native live gateway profiles Z.ai command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=zai node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-src-gateway-backends label: Native live gateway backends command: OPENCLAW_LIVE_CODEX_HARNESS=1 OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-backends timeout_minutes: 60 profile_env_only: false profiles: stable full - suite_id: native-live-src-infra label: Native live infra command: OPENCLAW_LIVE_APNS_REACHABILITY=1 node .release-harness/scripts/test-live-shard.mjs native-live-src-infra timeout_minutes: 45 profile_env_only: false profiles: stable full - suite_id: native-live-test label: Native live test harnesses command: node .release-harness/scripts/test-live-shard.mjs native-live-test timeout_minutes: 60 profile_env_only: false profiles: stable full - suite_id: native-live-extensions-l-n label: Native live plugins L-N command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-l-n timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-extensions-moonshot label: Native live Moonshot plugin command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-moonshot timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-extensions-openai label: Native live OpenAI plugin command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-openai timeout_minutes: 60 profile_env_only: false profiles: beta minimum stable full - suite_id: native-live-extensions-o-z-other label: Native live plugins O-Z other command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-o-z-other timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-extensions-xai label: Native live xAI plugin command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-xai timeout_minutes: 30 profile_env_only: false advisory: true profiles: full env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }} ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }} BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }} CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }} DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }} DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }} KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }} MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }} OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }} OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }} OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }} OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }} OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }} FAL_KEY: ${{ secrets.FAL_KEY }} RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }} DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }} BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }} BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }} OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }} OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }} OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }} OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }} OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }} OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }} FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }} OPENCLAW_LIVE_VIDEO_GENERATION_SKIP_PROVIDERS: "" OPENCLAW_LIVE_VYDRA_VIDEO: "1" OPENCLAW_VITEST_MAX_WORKERS: "2" steps: - name: Checkout selected ref if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-'))) uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Checkout trusted live shard harness if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-'))) uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.sha }} fetch-depth: 1 path: .release-harness - name: Setup Node environment if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-'))) uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Hydrate live auth/profile inputs if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-'))) run: bash scripts/ci-hydrate-live-auth.sh - name: Configure suite-specific env if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-'))) shell: bash run: | set -euo pipefail if [[ "${{ matrix.profile_env_only }}" == "true" ]]; then echo "OPENCLAW_DOCKER_PROFILE_ENV_ONLY=1" >> "$GITHUB_ENV" fi case "${{ matrix.suite_id }}" in live-cli-backend-docker) echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6" >> "$GITHUB_ENV" if [[ -n "${OPENCLAW_CLAUDE_CREDENTIALS_JSON:-}" || -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=subscription" >> "$GITHUB_ENV" else echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV" fi echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV" echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV" echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV" ;; live-codex-harness-docker) # Keep CI on the API-key path for now. The staged Codex auth secret # is currently stale, but the wrapper still supports codex-auth for # local maintainer reruns without changing Peter's flow. echo "OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key" >> "$GITHUB_ENV" echo "OPENCLAW_LIVE_CODEX_HARNESS_DEBUG=1" >> "$GITHUB_ENV" echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV" echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV" ;; live-acp-bind-docker) if [[ -n "${GEMINI_API_KEY:-}" || -n "${GOOGLE_API_KEY:-}" ]]; then echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex,gemini" >> "$GITHUB_ENV" else # The hydrated Gemini settings file only selects Gemini CLI auth # mode. CI still needs a usable Gemini or Google API key before # ACP bind can initialize a Gemini session. echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex" >> "$GITHUB_ENV" fi ;; esac - name: Run ${{ matrix.label }} if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-'))) env: OPENCLAW_LIVE_COMMAND: ${{ matrix.command }} OPENCLAW_LIVE_SUITE_ADVISORY: ${{ matrix.advisory }} shell: bash run: | set +e bash .release-harness/scripts/ci-live-command-retry.sh status=$? set -e if [[ "$status" -eq 0 ]]; then exit 0 fi if [[ "${OPENCLAW_LIVE_SUITE_ADVISORY:-}" == "true" ]]; then echo "::warning::Advisory live suite failed with exit code ${status}: ${{ matrix.suite_id }}" exit 0 fi exit "$status" validate_live_docker_provider_suites: name: Docker live suites (${{ matrix.label }}) needs: [validate_selected_ref, prepare_live_test_image] if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-')) continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }} timeout-minutes: ${{ matrix.timeout_minutes }} strategy: fail-fast: false matrix: include: - suite_id: live-gateway-docker label: Docker live gateway OpenAI command: OPENCLAW_LIVE_GATEWAY_THINKING=off OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.5 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=600000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh timeout_minutes: 40 profile_env_only: false profiles: beta minimum stable full - suite_id: live-gateway-anthropic-docker label: Docker live gateway Anthropic command: OPENCLAW_LIVE_GATEWAY_THINKING=low OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-sonnet-4-6,anthropic/claude-haiku-4-5 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=600000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh timeout_minutes: 40 profile_env_only: false profiles: stable full - suite_id: live-gateway-google-docker label: Docker live gateway Google command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=google OPENCLAW_LIVE_GATEWAY_MODELS=google/gemini-3.1-pro-preview OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh timeout_minutes: 40 profile_env_only: false profiles: stable full - suite_id: live-gateway-minimax-docker label: Docker live gateway MiniMax command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh timeout_minutes: 40 profile_env_only: false profiles: stable full - suite_id: live-gateway-advisory-docker-deepseek-fireworks suite_group: live-gateway-advisory-docker label: Docker live gateway advisory DeepSeek/Fireworks command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek,fireworks OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh timeout_minutes: 40 profile_env_only: false advisory: true profiles: full - suite_id: live-gateway-advisory-docker-opencode-openrouter suite_group: live-gateway-advisory-docker label: Docker live gateway advisory OpenCode/OpenRouter command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh timeout_minutes: 40 profile_env_only: false advisory: true profiles: full - suite_id: live-gateway-advisory-docker-xai-zai suite_group: live-gateway-advisory-docker label: Docker live gateway advisory xAI/Z.ai command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai,zai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh timeout_minutes: 40 profile_env_only: false advisory: true profiles: full - suite_id: live-cli-backend-docker label: Docker live CLI backend command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 45m bash .release-harness/scripts/test-live-cli-backend-docker.sh timeout_minutes: 50 profile_env_only: false profiles: stable full - suite_id: live-acp-bind-docker label: Docker live ACP bind command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 45m bash .release-harness/scripts/test-live-acp-bind-docker.sh timeout_minutes: 50 profile_env_only: false profiles: stable full - suite_id: live-codex-harness-docker label: Docker live Codex harness command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-codex-harness-docker.sh timeout_minutes: 40 profile_env_only: false profiles: stable full - suite_id: live-subagent-announce-docker label: Docker live subagent announce command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 20m bash .release-harness/scripts/test-live-subagent-announce-docker.sh timeout_minutes: 25 profile_env_only: false profiles: stable full env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }} ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }} BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }} CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }} DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }} DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }} KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }} MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }} OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }} OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }} OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }} OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }} OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }} FAL_KEY: ${{ secrets.FAL_KEY }} RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }} DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }} BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }} BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }} OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }} OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }} OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }} OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }} OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }} OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }} FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }} OPENCLAW_LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }} OPENCLAW_SKIP_DOCKER_BUILD: "1" OPENCLAW_LIVE_VIDEO_GENERATION_SKIP_PROVIDERS: "" OPENCLAW_LIVE_VYDRA_VIDEO: "1" OPENCLAW_VITEST_MAX_WORKERS: "2" steps: - name: Checkout selected ref if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-'))) uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Checkout trusted live shard harness if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-'))) uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.sha }} fetch-depth: 1 path: .release-harness - name: Setup Node environment if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-'))) uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Hydrate live auth/profile inputs if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-'))) run: bash scripts/ci-hydrate-live-auth.sh - name: Log in to GHCR if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-'))) run: bash .release-harness/scripts/ci-docker-login-ghcr.sh env: GHCR_USERNAME: ${{ github.actor }} GITHUB_TOKEN: ${{ github.token }} - name: Configure suite-specific env if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-'))) shell: bash run: | set -euo pipefail if [[ "${{ matrix.profile_env_only }}" == "true" ]]; then echo "OPENCLAW_DOCKER_PROFILE_ENV_ONLY=1" >> "$GITHUB_ENV" fi case "${{ matrix.suite_id }}" in live-cli-backend-docker) echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6" >> "$GITHUB_ENV" if [[ -n "${OPENCLAW_CLAUDE_CREDENTIALS_JSON:-}" || -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=subscription" >> "$GITHUB_ENV" else echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV" fi echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV" echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV" echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV" ;; live-codex-harness-docker) echo "OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key" >> "$GITHUB_ENV" echo "OPENCLAW_LIVE_CODEX_HARNESS_DEBUG=1" >> "$GITHUB_ENV" echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV" echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV" ;; live-acp-bind-docker) if [[ -n "${GEMINI_API_KEY:-}" || -n "${GOOGLE_API_KEY:-}" ]]; then echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex,gemini" >> "$GITHUB_ENV" else echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex" >> "$GITHUB_ENV" fi ;; esac - name: Run ${{ matrix.label }} if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-'))) env: OPENCLAW_LIVE_COMMAND: ${{ matrix.command }} OPENCLAW_LIVE_SUITE_ADVISORY: ${{ matrix.advisory }} shell: bash run: | set +e bash .release-harness/scripts/ci-live-command-retry.sh status=$? set -e if [[ "$status" -eq 0 ]]; then exit 0 fi if [[ "${OPENCLAW_LIVE_SUITE_ADVISORY:-}" == "true" ]]; then echo "::warning::Advisory live suite failed with exit code ${status}: ${{ matrix.suite_id }}" exit 0 fi exit "$status" validate_live_media_provider_suites: name: Live media suites (${{ matrix.label }}) needs: validate_selected_ref if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'native-live-extensions-media') || inputs.live_suite_filter == 'native-live-extensions-a-k') continue-on-error: ${{ inputs.advisory }} runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }} container: image: ghcr.io/openclaw/openclaw-live-media-runner:ubuntu-24.04 credentials: username: ${{ github.actor }} password: ${{ github.token }} timeout-minutes: ${{ matrix.timeout_minutes }} strategy: fail-fast: false matrix: include: - suite_id: native-live-extensions-a-k label: Native live plugins A-K command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-a-k timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-extensions-media-audio label: Native live media audio plugins command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-audio timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-extensions-media-music-google label: Native live media music Google command: OPENCLAW_LIVE_MUSIC_GENERATION_PROVIDERS=google node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-music-google timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-extensions-media-music-minimax label: Native live media music MiniMax command: OPENCLAW_LIVE_MUSIC_GENERATION_PROVIDERS=minimax node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-music-minimax timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-extensions-media-video-a suite_group: native-live-extensions-media-video label: Native live media video plugins A command: OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS=alibaba,byteplus,deepinfra,fal node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-video timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-extensions-media-video-b suite_group: native-live-extensions-media-video label: Native live media video plugins B command: OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS=google,minimax node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-video timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-extensions-media-video-c suite_group: native-live-extensions-media-video label: Native live media video plugins C command: OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS=openai,openrouter,xai node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-video timeout_minutes: 30 profile_env_only: false advisory: true profiles: full - suite_id: native-live-extensions-media-video-d suite_group: native-live-extensions-media-video label: Native live media video plugins D command: OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS=qwen,runway,together,vydra node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-video timeout_minutes: 30 profile_env_only: false advisory: true profiles: full env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }} ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }} BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }} CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }} DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }} DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }} KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }} MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }} OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }} OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }} OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }} OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }} OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }} FAL_KEY: ${{ secrets.FAL_KEY }} RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }} DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }} BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }} BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }} OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }} OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }} OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }} OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }} OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }} OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }} FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }} OPENCLAW_LIVE_VIDEO_GENERATION_SKIP_PROVIDERS: "" OPENCLAW_LIVE_VYDRA_VIDEO: "1" OPENCLAW_VITEST_MAX_WORKERS: "2" steps: - name: Checkout selected ref if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-'))) uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.validate_selected_ref.outputs.selected_sha }} fetch-depth: 1 - name: Checkout trusted live shard harness if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-'))) uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.sha }} fetch-depth: 1 path: .release-harness - name: Verify preinstalled live media dependencies if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-'))) shell: bash run: | set -euo pipefail ffmpeg -version | head -1 ffprobe -version | head -1 - name: Setup Node environment if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-'))) uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: "true" - name: Hydrate live auth/profile inputs if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-'))) run: bash scripts/ci-hydrate-live-auth.sh - name: Configure suite-specific env if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-'))) shell: bash run: | set -euo pipefail if [[ "${{ matrix.profile_env_only }}" == "true" ]]; then echo "OPENCLAW_DOCKER_PROFILE_ENV_ONLY=1" >> "$GITHUB_ENV" fi - name: Run ${{ matrix.label }} if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-'))) env: OPENCLAW_LIVE_SUITE_ADVISORY: ${{ matrix.advisory }} shell: bash run: | set +e ${{ matrix.command }} status=$? set -e if [[ "$status" -eq 0 ]]; then exit 0 fi if [[ "${OPENCLAW_LIVE_SUITE_ADVISORY:-}" == "true" ]]; then echo "::warning::Advisory live suite failed with exit code ${status}: ${{ matrix.suite_id }}" exit 0 fi exit "$status"