mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-18 12:01:34 +00:00
3643 lines
180 KiB
YAML
3643 lines
180 KiB
YAML
name: OpenClaw Live And E2E Checks (Reusable)
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
ref:
|
|
description: Ref, tag, or SHA to validate
|
|
required: true
|
|
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
|
|
shared_image_policy:
|
|
description: Shared Docker image transport
|
|
required: true
|
|
default: no-push-artifact
|
|
type: choice
|
|
options:
|
|
- existing-only
|
|
- no-push-artifact
|
|
shared_image_artifact_namespace:
|
|
description: Safe unique artifact namespace when shared_image_policy=no-push-artifact
|
|
required: false
|
|
default: direct
|
|
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 where supported; OpenWebUI always uses a large-disk Blacksmith runner
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
advisory:
|
|
description: Treat failures as advisory for the caller
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
live_advisory:
|
|
description: Treat only live-provider suite failures as advisory for the caller; repo and Docker E2E stay blocking
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
allow_unreleased_changelog:
|
|
description: Allow current-tree QA packaging to use Unreleased notes when no exact version section exists
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
workflow_call:
|
|
inputs:
|
|
advisory:
|
|
description: Treat failures as advisory for the caller
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
live_advisory:
|
|
description: Treat only live-provider suite failures as advisory for the caller; repo and Docker E2E stay blocking
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
allow_unreleased_changelog:
|
|
description: Allow current-tree QA packaging to use Unreleased notes when no exact version section exists
|
|
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_id:
|
|
description: Immutable GitHub artifact id for package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_digest:
|
|
description: GitHub artifact service SHA-256 digest without the sha256 prefix
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_run_id:
|
|
description: Producer run id containing package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_run_attempt:
|
|
description: Producer run attempt containing package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_file_name:
|
|
description: Exact package tarball filename inside package_artifact_name
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_source_sha:
|
|
description: Exact source commit recorded in the package tarball
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_sha256:
|
|
description: Exact root OpenClaw package SHA-256 for no-push image artifacts
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_version:
|
|
description: Exact OpenClaw package version for no-push image artifacts
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
shared_image_policy:
|
|
description: "Shared Docker image transport: existing-only or no-push-artifact"
|
|
required: false
|
|
default: no-push-artifact
|
|
type: string
|
|
shared_image_artifact_namespace:
|
|
description: Safe unique artifact namespace when shared_image_policy=no-push-artifact
|
|
required: false
|
|
default: direct
|
|
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 where supported; OpenWebUI always uses a large-disk Blacksmith runner
|
|
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:
|
|
actions: read
|
|
contents: read
|
|
packages: read
|
|
pull-requests: read
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
NODE_VERSION: "24.15.0"
|
|
OPENCLAW_DOCKER_E2E_ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog }}
|
|
|
|
jobs:
|
|
validate_selected_ref:
|
|
runs-on: ubuntu-24.04
|
|
outputs:
|
|
selected_sha: ${{ steps.validate.outputs.selected_sha }}
|
|
trusted_reason: ${{ steps.validate.outputs.trusted_reason }}
|
|
workflow_repository: ${{ steps.workflow.outputs.workflow_repository }}
|
|
workflow_sha: ${{ steps.workflow.outputs.workflow_sha }}
|
|
steps:
|
|
# github.workflow_sha identifies the caller during workflow_call. Resolve the called
|
|
# workflow SHA from job context so trusted harness checkouts cannot drift to candidate code.
|
|
- name: Resolve job workflow identity
|
|
id: workflow
|
|
env:
|
|
JOB_CONTEXT: ${{ toJSON(job) }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
node --input-type=module <<'NODE'
|
|
import fs from "node:fs";
|
|
|
|
const job = JSON.parse(process.env.JOB_CONTEXT ?? "{}");
|
|
if (
|
|
typeof job.workflow_repository !== "string" ||
|
|
!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(job.workflow_repository)
|
|
) {
|
|
throw new Error("job.workflow_repository must be an owner/repository slug");
|
|
}
|
|
if (typeof job.workflow_sha !== "string" || !/^[0-9a-f]{40}$/u.test(job.workflow_sha)) {
|
|
throw new Error("job.workflow_sha must be a full lowercase commit SHA");
|
|
}
|
|
const outputPath = process.env.GITHUB_OUTPUT;
|
|
if (!outputPath) {
|
|
throw new Error("GITHUB_OUTPUT is required");
|
|
}
|
|
fs.appendFileSync(
|
|
outputPath,
|
|
`workflow_repository=${job.workflow_repository}\nworkflow_sha=${job.workflow_sha}\n`,
|
|
);
|
|
NODE
|
|
|
|
- name: Checkout workflow repository
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Validate selected ref
|
|
id: validate
|
|
env:
|
|
INPUT_REF: ${{ inputs.ref }}
|
|
PACKAGE_ARTIFACT_DIGEST: ${{ inputs.package_artifact_digest }}
|
|
PACKAGE_ARTIFACT_ID: ${{ inputs.package_artifact_id }}
|
|
PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name }}
|
|
PACKAGE_ARTIFACT_RUN_ATTEMPT: ${{ inputs.package_artifact_run_attempt }}
|
|
PACKAGE_ARTIFACT_RUN_ID: ${{ inputs.package_artifact_run_id }}
|
|
PACKAGE_FILE_NAME: ${{ inputs.package_file_name }}
|
|
PACKAGE_SHA256: ${{ inputs.package_sha256 }}
|
|
PACKAGE_SOURCE_SHA: ${{ inputs.package_source_sha }}
|
|
PACKAGE_VERSION: ${{ inputs.package_version }}
|
|
PROVIDED_BARE_IMAGE: ${{ inputs.docker_e2e_bare_image }}
|
|
PROVIDED_FUNCTIONAL_IMAGE: ${{ inputs.docker_e2e_functional_image }}
|
|
SHARED_IMAGE_ARTIFACT_NAMESPACE: ${{ inputs.shared_image_artifact_namespace }}
|
|
SHARED_IMAGE_POLICY: ${{ inputs.shared_image_policy }}
|
|
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
|
|
|
|
package_tuple_present=0
|
|
for value in \
|
|
"$PACKAGE_ARTIFACT_DIGEST" \
|
|
"$PACKAGE_ARTIFACT_ID" \
|
|
"$PACKAGE_ARTIFACT_NAME" \
|
|
"$PACKAGE_ARTIFACT_RUN_ATTEMPT" \
|
|
"$PACKAGE_ARTIFACT_RUN_ID" \
|
|
"$PACKAGE_FILE_NAME" \
|
|
"$PACKAGE_SHA256" \
|
|
"$PACKAGE_SOURCE_SHA" \
|
|
"$PACKAGE_VERSION"; do
|
|
if [[ -n "${value// }" ]]; then
|
|
package_tuple_present=1
|
|
fi
|
|
done
|
|
if [[ "$package_tuple_present" == "1" ]]; then
|
|
[[ "$PACKAGE_ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ &&
|
|
"$PACKAGE_ARTIFACT_ID" =~ ^[1-9][0-9]*$ &&
|
|
-n "${PACKAGE_ARTIFACT_NAME// }" &&
|
|
"$PACKAGE_ARTIFACT_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ &&
|
|
"$PACKAGE_ARTIFACT_RUN_ID" =~ ^[1-9][0-9]*$ &&
|
|
"$PACKAGE_FILE_NAME" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*\.tgz$ &&
|
|
"$PACKAGE_SHA256" =~ ^[0-9a-f]{64}$ &&
|
|
"$PACKAGE_SOURCE_SHA" =~ ^[0-9a-f]{40}$ &&
|
|
-n "${PACKAGE_VERSION// }" ]] || {
|
|
echo "Package artifact selection requires the complete immutable artifact and package identity tuple." >&2
|
|
exit 1
|
|
}
|
|
[[ "$PACKAGE_SOURCE_SHA" == "$selected_sha" ]] || {
|
|
echo "Package source SHA differs from the selected release SHA." >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
|
|
case "$SHARED_IMAGE_POLICY" in
|
|
existing-only)
|
|
if [[ -z "${PROVIDED_BARE_IMAGE// }" && -z "${PROVIDED_FUNCTIONAL_IMAGE// }" ]]; then
|
|
echo "shared_image_policy=existing-only requires explicit shared image refs." >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
no-push-artifact)
|
|
[[ "$INPUT_REF" =~ ^[0-9a-f]{40}$ && "$INPUT_REF" == "$selected_sha" ]] || {
|
|
echo "shared_image_policy=no-push-artifact requires ref to be the exact lowercase target SHA." >&2
|
|
exit 1
|
|
}
|
|
[[ "$SHARED_IMAGE_ARTIFACT_NAMESPACE" =~ ^[a-z0-9][a-z0-9-]{0,47}$ ]] || {
|
|
echo "shared_image_artifact_namespace must be a lowercase slug up to 48 characters." >&2
|
|
exit 1
|
|
}
|
|
[[ -z "${PROVIDED_BARE_IMAGE// }" && -z "${PROVIDED_FUNCTIONAL_IMAGE// }" ]] || {
|
|
echo "shared_image_policy=no-push-artifact builds local image artifacts and rejects provided images." >&2
|
|
exit 1
|
|
}
|
|
;;
|
|
*)
|
|
echo "shared_image_policy must be existing-only or no-push-artifact." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
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-openai-api-default "stable full"
|
|
add_profile_suite native-live-src-gateway-profiles-openai-gpt56-ultra "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-anthropic-docker-full "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-codex-harness-gpt56-sol-docker "stable full"
|
|
add_profile_suite live-codex-harness-gpt56-terra-docker "stable full"
|
|
add_profile_suite live-codex-harness-gpt56-luna-docker "stable full"
|
|
add_profile_suite live-codex-harness-gpt56-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
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_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 || inputs.live_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,
|
|
docker_e2e_image_ready,
|
|
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_DOCKER_E2E_REQUIRE_LOCAL_IMAGE: ${{ inputs.shared_image_policy == 'no-push-artifact' && '1' || '0' }}
|
|
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
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_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) && inputs.shared_image_policy != 'no-push-artifact'
|
|
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:
|
|
artifact-ids: ${{ needs.prepare_docker_e2e_image.outputs.package_artifact_id }}
|
|
path: .artifacts/docker-e2e-package
|
|
run-id: ${{ needs.prepare_docker_e2e_image.outputs.package_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Validate Docker E2E image artifact binding
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && inputs.shared_image_policy == 'no-push-artifact' && needs.prepare_docker_e2e_image.outputs.needs_e2e_image == '1'
|
|
env:
|
|
ARTIFACT_DIGEST: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_digest }}
|
|
ARTIFACT_ID: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_id }}
|
|
ARTIFACT_NAME: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_name }}
|
|
ARTIFACT_RUN_ATTEMPT: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_attempt }}
|
|
ARTIFACT_RUN_ID: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_id }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
verify-upload "Docker E2E image" \
|
|
"$ARTIFACT_ID" "$ARTIFACT_NAME" "$ARTIFACT_DIGEST" \
|
|
"$ARTIFACT_RUN_ID" "$ARTIFACT_RUN_ATTEMPT"
|
|
|
|
- name: Download Docker E2E image artifact
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && inputs.shared_image_policy == 'no-push-artifact' && needs.prepare_docker_e2e_image.outputs.needs_e2e_image == '1'
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
artifact-ids: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_id }}
|
|
path: .artifacts/docker-e2e-images
|
|
run-id: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Verify and load Docker E2E image artifact
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && inputs.shared_image_policy == 'no-push-artifact' && needs.prepare_docker_e2e_image.outputs.needs_e2e_image == '1'
|
|
env:
|
|
BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }}
|
|
FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
|
|
NEEDS_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.needs_bare_image }}
|
|
NEEDS_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.needs_functional_image }}
|
|
PACKAGE_SHA256: ${{ needs.prepare_docker_e2e_image.outputs.package_sha256 }}
|
|
ARCHIVE_SHA256: ${{ needs.prepare_docker_e2e_image.outputs.image_archive_sha256 }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ATTEMPT: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_attempt }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ID: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_id }}
|
|
TARGET_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
WORKFLOW_SHA: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
images=()
|
|
if [[ "$NEEDS_BARE_IMAGE" == "1" ]]; then
|
|
images+=("$BARE_IMAGE")
|
|
fi
|
|
if [[ "$NEEDS_FUNCTIONAL_IMAGE" == "1" ]]; then
|
|
images+=("$FUNCTIONAL_IMAGE")
|
|
fi
|
|
OPENCLAW_SHARED_IMAGE_ARCHIVE_SHA256="$ARCHIVE_SHA256" \
|
|
OPENCLAW_SHARED_IMAGE_PACKAGE_SHA256="$PACKAGE_SHA256" \
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
load .artifacts/docker-e2e-images docker-e2e "$TARGET_SHA" "$WORKFLOW_SHA" "${images[@]}"
|
|
|
|
- name: Pull shared bare Docker E2E image
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && inputs.shared_image_policy != 'no-push-artifact' && 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) && inputs.shared_image_policy != 'no-push-artifact' && 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_SKIP_DOCKER_BUILD=0 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
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_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,
|
|
docker_e2e_image_ready,
|
|
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_DOCKER_E2E_REQUIRE_LOCAL_IMAGE: ${{ inputs.shared_image_policy == 'no-push-artifact' && '1' || '0' }}
|
|
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
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
|
|
- name: Log in to GHCR for shared Docker E2E image
|
|
if: inputs.shared_image_policy != 'no-push-artifact'
|
|
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:
|
|
artifact-ids: ${{ needs.prepare_docker_e2e_image.outputs.package_artifact_id }}
|
|
path: .artifacts/docker-e2e-package
|
|
run-id: ${{ needs.prepare_docker_e2e_image.outputs.package_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Validate Docker E2E image artifact binding
|
|
if: inputs.shared_image_policy == 'no-push-artifact' && needs.prepare_docker_e2e_image.outputs.needs_e2e_image == '1'
|
|
env:
|
|
ARTIFACT_DIGEST: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_digest }}
|
|
ARTIFACT_ID: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_id }}
|
|
ARTIFACT_NAME: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_name }}
|
|
ARTIFACT_RUN_ATTEMPT: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_attempt }}
|
|
ARTIFACT_RUN_ID: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_id }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
verify-upload "Docker E2E image" \
|
|
"$ARTIFACT_ID" "$ARTIFACT_NAME" "$ARTIFACT_DIGEST" \
|
|
"$ARTIFACT_RUN_ID" "$ARTIFACT_RUN_ATTEMPT"
|
|
|
|
- name: Download Docker E2E image artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact' && needs.prepare_docker_e2e_image.outputs.needs_e2e_image == '1'
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
artifact-ids: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_id }}
|
|
path: .artifacts/docker-e2e-images
|
|
run-id: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Verify and load Docker E2E image artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact' && needs.prepare_docker_e2e_image.outputs.needs_e2e_image == '1'
|
|
env:
|
|
BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }}
|
|
FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
|
|
NEEDS_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.needs_bare_image }}
|
|
NEEDS_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.needs_functional_image }}
|
|
PACKAGE_SHA256: ${{ needs.prepare_docker_e2e_image.outputs.package_sha256 }}
|
|
ARCHIVE_SHA256: ${{ needs.prepare_docker_e2e_image.outputs.image_archive_sha256 }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ATTEMPT: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_attempt }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ID: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_id }}
|
|
TARGET_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
WORKFLOW_SHA: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
images=()
|
|
if [[ "$NEEDS_BARE_IMAGE" == "1" ]]; then
|
|
images+=("$BARE_IMAGE")
|
|
fi
|
|
if [[ "$NEEDS_FUNCTIONAL_IMAGE" == "1" ]]; then
|
|
images+=("$FUNCTIONAL_IMAGE")
|
|
fi
|
|
OPENCLAW_SHARED_IMAGE_ARCHIVE_SHA256="$ARCHIVE_SHA256" \
|
|
OPENCLAW_SHARED_IMAGE_PACKAGE_SHA256="$PACKAGE_SHA256" \
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
load .artifacts/docker-e2e-images docker-e2e "$TARGET_SHA" "$WORKFLOW_SHA" "${images[@]}"
|
|
|
|
- name: Pull shared bare Docker E2E image
|
|
if: inputs.shared_image_policy != 'no-push-artifact' && 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: inputs.shared_image_policy != 'no-push-artifact' && 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
|
|
env:
|
|
ARTIFACT_SUFFIX: ${{ steps.plan.outputs.artifact_suffix }}
|
|
INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include_release_path_suites }}
|
|
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 [[ "$INCLUDE_RELEASE_PATH_SUITES" == "true" ]]; then
|
|
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
|
fi
|
|
export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/targeted-${ARTIFACT_SUFFIX}"
|
|
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/targeted-${ARTIFACT_SUFFIX}-timings.json"
|
|
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
|
|
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
|
|
OPENCLAW_SKIP_DOCKER_BUILD=0 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, docker_e2e_image_ready]
|
|
if: inputs.include_openwebui && inputs.docker_lanes == '' && (inputs.release_test_profile == 'stable' || inputs.release_test_profile == 'full')
|
|
name: Docker E2E (openwebui)
|
|
continue-on-error: ${{ inputs.advisory }}
|
|
runs-on: 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_DOCKER_ALL_RELEASE_PROFILE: ${{ inputs.release_test_profile }}
|
|
OPENCLAW_DOCKER_E2E_REQUIRE_LOCAL_IMAGE: ${{ inputs.shared_image_policy == 'no-push-artifact' && '1' || '0' }}
|
|
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
|
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:
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
persist-credentials: false
|
|
|
|
- name: Log in to GHCR for shared Docker E2E image
|
|
if: inputs.shared_image_policy != 'no-push-artifact'
|
|
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: "false"
|
|
install-deps: "false"
|
|
use-actions-cache: "false"
|
|
|
|
- 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:
|
|
artifact-ids: ${{ needs.prepare_docker_e2e_image.outputs.package_artifact_id }}
|
|
path: .artifacts/docker-e2e-package
|
|
run-id: ${{ needs.prepare_docker_e2e_image.outputs.package_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Validate Docker E2E image artifact binding
|
|
if: inputs.shared_image_policy == 'no-push-artifact' && needs.prepare_docker_e2e_image.outputs.needs_e2e_image == '1'
|
|
env:
|
|
ARTIFACT_DIGEST: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_digest }}
|
|
ARTIFACT_ID: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_id }}
|
|
ARTIFACT_NAME: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_name }}
|
|
ARTIFACT_RUN_ATTEMPT: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_attempt }}
|
|
ARTIFACT_RUN_ID: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_id }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
verify-upload "Docker E2E image" \
|
|
"$ARTIFACT_ID" "$ARTIFACT_NAME" "$ARTIFACT_DIGEST" \
|
|
"$ARTIFACT_RUN_ID" "$ARTIFACT_RUN_ATTEMPT"
|
|
|
|
- name: Download Docker E2E image artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact' && needs.prepare_docker_e2e_image.outputs.needs_e2e_image == '1'
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
artifact-ids: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_id }}
|
|
path: .artifacts/docker-e2e-images
|
|
run-id: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Verify and load Docker E2E image artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact' && needs.prepare_docker_e2e_image.outputs.needs_e2e_image == '1'
|
|
env:
|
|
BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }}
|
|
FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
|
|
NEEDS_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.needs_bare_image }}
|
|
NEEDS_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.needs_functional_image }}
|
|
PACKAGE_SHA256: ${{ needs.prepare_docker_e2e_image.outputs.package_sha256 }}
|
|
ARCHIVE_SHA256: ${{ needs.prepare_docker_e2e_image.outputs.image_archive_sha256 }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ATTEMPT: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_attempt }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ID: ${{ needs.prepare_docker_e2e_image.outputs.image_artifact_run_id }}
|
|
TARGET_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
WORKFLOW_SHA: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
images=()
|
|
if [[ "$NEEDS_BARE_IMAGE" == "1" ]]; then
|
|
images+=("$BARE_IMAGE")
|
|
fi
|
|
if [[ "$NEEDS_FUNCTIONAL_IMAGE" == "1" ]]; then
|
|
images+=("$FUNCTIONAL_IMAGE")
|
|
fi
|
|
OPENCLAW_SHARED_IMAGE_ARCHIVE_SHA256="$ARCHIVE_SHA256" \
|
|
OPENCLAW_SHARED_IMAGE_PACKAGE_SHA256="$PACKAGE_SHA256" \
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
load .artifacts/docker-e2e-images docker-e2e "$TARGET_SHA" "$WORKFLOW_SHA" "${images[@]}"
|
|
|
|
- name: Pull shared bare Docker E2E image
|
|
if: inputs.shared_image_policy != 'no-push-artifact' && 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: inputs.shared_image_policy != 'no-push-artifact' && 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_SKIP_DOCKER_BUILD=0 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: read
|
|
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 }}
|
|
bare_exists: ${{ steps.image_exists.outputs.bare_exists }}
|
|
functional_exists: ${{ steps.image_exists.outputs.functional_exists }}
|
|
needs_registry_build: ${{ steps.image_exists.outputs.needs_build }}
|
|
package_sha256: ${{ steps.package.outputs.sha256 }}
|
|
package_version: ${{ steps.package.outputs.version }}
|
|
package_artifact_name: ${{ steps.upload_package.outputs.artifact-id && format('docker-e2e-package-{0}-{1}', github.run_id, github.run_attempt) || inputs.package_artifact_name }}
|
|
package_artifact_id: ${{ steps.upload_package.outputs.artifact-id || inputs.package_artifact_id }}
|
|
package_artifact_digest: ${{ steps.upload_package.outputs.artifact-digest || steps.input_package_artifact.outputs.artifact_digest }}
|
|
package_artifact_run_id: ${{ steps.upload_package.outputs.artifact-id && github.run_id || steps.input_package_artifact.outputs.run_id }}
|
|
package_artifact_run_attempt: ${{ steps.upload_package.outputs.artifact-id && github.run_attempt || steps.input_package_artifact.outputs.run_attempt }}
|
|
package_file_name: ${{ steps.package.outputs.file_name }}
|
|
package_source_sha: ${{ steps.package.outputs.source_sha }}
|
|
image_artifact_name: ${{ steps.image_artifact.outputs.artifact_name }}
|
|
image_archive_sha256: ${{ steps.image_artifact.outputs.archive_sha256 }}
|
|
image_artifact_id: ${{ steps.upload_image_artifact.outputs.artifact-id }}
|
|
image_artifact_digest: ${{ steps.upload_image_artifact.outputs.artifact-digest }}
|
|
image_artifact_run_id: ${{ github.run_id }}
|
|
image_artifact_run_attempt: ${{ github.run_attempt }}
|
|
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
|
|
persist-credentials: false
|
|
|
|
- name: Checkout trusted release harness
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
persist-credentials: false
|
|
|
|
- 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: Validate OpenClaw package artifact identity
|
|
id: input_package_artifact
|
|
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_id != ''
|
|
env:
|
|
ARTIFACT_DIGEST: ${{ inputs.package_artifact_digest }}
|
|
ARTIFACT_ID: ${{ inputs.package_artifact_id }}
|
|
ARTIFACT_NAME: ${{ inputs.package_artifact_name }}
|
|
ARTIFACT_RUN_ATTEMPT: ${{ inputs.package_artifact_run_attempt }}
|
|
ARTIFACT_RUN_ID: ${{ inputs.package_artifact_run_id }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
[[ "$ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ &&
|
|
"$ARTIFACT_ID" =~ ^[1-9][0-9]*$ &&
|
|
-n "${ARTIFACT_NAME// }" &&
|
|
"$ARTIFACT_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ &&
|
|
"$ARTIFACT_RUN_ID" =~ ^[1-9][0-9]*$ ]] || {
|
|
echo "OpenClaw package artifact identity tuple is incomplete." >&2
|
|
exit 1
|
|
}
|
|
[[ "$ARTIFACT_NAME" == *"-${ARTIFACT_RUN_ID}-${ARTIFACT_RUN_ATTEMPT}" ]] || {
|
|
echo "OpenClaw package artifact name does not bind the declared producer run attempt." >&2
|
|
exit 1
|
|
}
|
|
artifact_json="$(gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}")"
|
|
jq -e \
|
|
--arg digest "sha256:${ARTIFACT_DIGEST}" \
|
|
--arg id "$ARTIFACT_ID" \
|
|
--arg name "$ARTIFACT_NAME" \
|
|
--arg run_id "$ARTIFACT_RUN_ID" \
|
|
'
|
|
(.id | tostring) == $id and
|
|
.name == $name and
|
|
.expired == false and
|
|
.digest == $digest and
|
|
(.workflow_run.id | tostring) == $run_id
|
|
' <<< "$artifact_json" >/dev/null || {
|
|
echo "OpenClaw package artifact identity does not match the requested immutable tuple." >&2
|
|
exit 1
|
|
}
|
|
attempt_json="$(
|
|
gh api \
|
|
"repos/${GITHUB_REPOSITORY}/actions/runs/${ARTIFACT_RUN_ID}/attempts/${ARTIFACT_RUN_ATTEMPT}"
|
|
)"
|
|
jq -e \
|
|
--arg attempt "$ARTIFACT_RUN_ATTEMPT" \
|
|
--arg run_id "$ARTIFACT_RUN_ID" \
|
|
'(.id | tostring) == $run_id and (.run_attempt | tostring) == $attempt' \
|
|
<<< "$attempt_json" >/dev/null || {
|
|
echo "OpenClaw package artifact producer run attempt does not match the requested tuple." >&2
|
|
exit 1
|
|
}
|
|
{
|
|
echo "artifact_digest=$ARTIFACT_DIGEST"
|
|
echo "run_id=$ARTIFACT_RUN_ID"
|
|
echo "run_attempt=$ARTIFACT_RUN_ATTEMPT"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Download current-run OpenClaw Docker E2E package
|
|
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_id != '' && inputs.package_artifact_run_id == github.run_id
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
artifact-ids: ${{ inputs.package_artifact_id }}
|
|
path: .artifacts/docker-e2e-package
|
|
run-id: ${{ inputs.package_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Download previous-run OpenClaw Docker E2E package
|
|
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_id != '' && inputs.package_artifact_run_id != github.run_id
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
artifact-ids: ${{ inputs.package_artifact_id }}
|
|
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 == ''
|
|
env:
|
|
ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p .artifacts/docker-e2e-package
|
|
package_args=(
|
|
--output-dir .artifacts/docker-e2e-package
|
|
--output-name openclaw-current.tgz
|
|
)
|
|
# Frozen targets that predate the opt-in flag still strict-pack against their versioned changelog.
|
|
if [[ "$ALLOW_UNRELEASED_CHANGELOG" == "true" ]] && \
|
|
grep -Fq -- '--allow-unreleased-changelog' scripts/package-openclaw-for-docker.mjs; then
|
|
package_args+=(--allow-unreleased-changelog)
|
|
fi
|
|
node scripts/package-openclaw-for-docker.mjs "${package_args[@]}"
|
|
|
|
- name: Validate OpenClaw Docker E2E package
|
|
id: package
|
|
if: steps.plan.outputs.needs_package == '1'
|
|
env:
|
|
EXPECTED_PACKAGE_FILE_NAME: ${{ inputs.package_file_name }}
|
|
EXPECTED_PACKAGE_SHA256: ${{ inputs.package_sha256 }}
|
|
EXPECTED_PACKAGE_SOURCE_SHA: ${{ inputs.package_source_sha }}
|
|
EXPECTED_PACKAGE_VERSION: ${{ inputs.package_version }}
|
|
SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
SHARED_IMAGE_POLICY: ${{ inputs.shared_image_policy }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p .artifacts/docker-e2e-package
|
|
target=".artifacts/docker-e2e-package/openclaw-current.tgz"
|
|
if [[ -n "${EXPECTED_PACKAGE_FILE_NAME// }" ]]; then
|
|
input_target=".artifacts/docker-e2e-package/${EXPECTED_PACKAGE_FILE_NAME}"
|
|
[[ -f "$input_target" ]] || {
|
|
echo "Declared package tarball is missing from the selected artifact." >&2
|
|
exit 1
|
|
}
|
|
input_digest="$(sha256sum "$input_target" | awk '{print $1}')"
|
|
[[ "$input_digest" == "$EXPECTED_PACKAGE_SHA256" ]] || {
|
|
echo "Declared package tarball SHA-256 differs from package_sha256." >&2
|
|
exit 1
|
|
}
|
|
if [[ "$input_target" != "$target" ]]; then
|
|
cp "$input_target" "$target"
|
|
fi
|
|
fi
|
|
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}')"
|
|
version="$(tar -xOf "$target" package/package.json | jq -r '.version')"
|
|
name="$(tar -xOf "$target" package/package.json | jq -r '.name')"
|
|
package_source_sha="$(
|
|
tar -xOf "$target" package/dist/build-info.json |
|
|
jq -er '.commit | select(type == "string" and test("^[0-9a-f]{40}$"))'
|
|
)"
|
|
if [[ -n "${EXPECTED_PACKAGE_SOURCE_SHA// }" ]]; then
|
|
[[ "$digest" == "$EXPECTED_PACKAGE_SHA256" &&
|
|
"$name" == "openclaw" &&
|
|
"$package_source_sha" == "$EXPECTED_PACKAGE_SOURCE_SHA" &&
|
|
"$version" == "$EXPECTED_PACKAGE_VERSION" ]] || {
|
|
echo "Resolved package identity differs from the declared immutable tuple." >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
if [[ "$SHARED_IMAGE_POLICY" == "no-push-artifact" ]]; then
|
|
if [[ -n "$EXPECTED_PACKAGE_SHA256" && "$digest" != "$EXPECTED_PACKAGE_SHA256" ]]; then
|
|
echo "Exact-target package SHA-256 differs from package_sha256." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "$name" != "openclaw" || ( -n "$EXPECTED_PACKAGE_VERSION" && "$version" != "$EXPECTED_PACKAGE_VERSION" ) ]]; then
|
|
echo "Exact-target package name/version differs from declared package identity." >&2
|
|
exit 1
|
|
fi
|
|
[[ "$package_source_sha" == "$SELECTED_SHA" ]] || {
|
|
echo "Exact-target package build commit differs from the selected SHA." >&2
|
|
exit 1
|
|
}
|
|
metadata=".artifacts/docker-e2e-package/package-candidate.json"
|
|
if [[ -f "$metadata" ]]; then
|
|
jq -e \
|
|
--arg sha "$SELECTED_SHA" \
|
|
--arg digest "$digest" \
|
|
--arg version "$version" \
|
|
'
|
|
.name == "openclaw" and
|
|
.packageSourceSha == $sha and
|
|
.sha256 == $digest and
|
|
.version == $version
|
|
' "$metadata" >/dev/null || {
|
|
echo "Exact-target package metadata does not bind the selected SHA and tarball." >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
fi
|
|
tag="pkg-${digest:0:32}"
|
|
echo "sha256=$digest" >> "$GITHUB_OUTPUT"
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
echo "file_name=openclaw-current.tgz" >> "$GITHUB_OUTPUT"
|
|
echo "source_sha=$package_source_sha" >> "$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
|
|
id: upload_package
|
|
if: steps.plan.outputs.needs_package == '1' && (inputs.package_artifact_id == '' || inputs.package_artifact_run_id != github.run_id)
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: docker-e2e-package-${{ github.run_id }}-${{ github.run_attempt }}
|
|
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 }}
|
|
SHARED_IMAGE_POLICY: ${{ inputs.shared_image_policy }}
|
|
run: |
|
|
set -euo pipefail
|
|
repository="${GITHUB_REPOSITORY,,}"
|
|
image_tag="${PACKAGE_TAG:-$SELECTED_SHA}"
|
|
if [[ "$SHARED_IMAGE_POLICY" == "no-push-artifact" ]]; then
|
|
bare_image="openclaw-docker-e2e-bare:${image_tag}"
|
|
functional_image="openclaw-docker-e2e-functional:${image_tag}"
|
|
else
|
|
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}}"
|
|
fi
|
|
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' && inputs.shared_image_policy == 'existing-only'
|
|
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' && inputs.shared_image_policy == 'existing-only'
|
|
shell: bash
|
|
env:
|
|
PROVIDED_BARE_IMAGE: ${{ inputs.docker_e2e_bare_image }}
|
|
PROVIDED_FUNCTIONAL_IMAGE: ${{ inputs.docker_e2e_functional_image }}
|
|
SHARED_IMAGE_POLICY: ${{ inputs.shared_image_policy }}
|
|
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
|
|
|
|
if [[ "$SHARED_IMAGE_POLICY" == "existing-only" && "$needs_build" == "1" ]]; then
|
|
echo "shared_image_policy=existing-only forbids building or pushing missing shared images." >&2
|
|
exit 1
|
|
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: inputs.shared_image_policy == 'no-push-artifact' && steps.plan.outputs.needs_e2e_image == '1'
|
|
uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1
|
|
with:
|
|
max-cache-size-mb: 800000
|
|
|
|
- name: Build bare Docker E2E image artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact' && steps.plan.outputs.needs_bare_image == '1'
|
|
shell: bash
|
|
env:
|
|
IMAGE_REF: ${{ steps.image.outputs.bare_image }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Docker's local image exporter cannot load attestation manifest lists.
|
|
timeout --kill-after=30s 45m docker buildx build \
|
|
--load \
|
|
--file ./scripts/e2e/Dockerfile \
|
|
--target bare \
|
|
--platform linux/amd64 \
|
|
--tag "$IMAGE_REF" \
|
|
--sbom=false \
|
|
--provenance=false \
|
|
.
|
|
|
|
- name: Build functional Docker E2E image artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact' && steps.plan.outputs.needs_functional_image == '1'
|
|
shell: bash
|
|
env:
|
|
IMAGE_REF: ${{ steps.image.outputs.functional_image }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Docker's local image exporter cannot load attestation manifest lists.
|
|
timeout --kill-after=30s 45m docker buildx build \
|
|
--load \
|
|
--file ./scripts/e2e/Dockerfile \
|
|
--target functional \
|
|
--build-context openclaw_package=.artifacts/docker-e2e-package \
|
|
--platform linux/amd64 \
|
|
--tag "$IMAGE_REF" \
|
|
--sbom=false \
|
|
--provenance=false \
|
|
.
|
|
|
|
- name: Pack Docker E2E image artifact
|
|
id: image_artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact' && steps.plan.outputs.needs_e2e_image == '1'
|
|
shell: bash
|
|
env:
|
|
BARE_IMAGE: ${{ steps.image.outputs.bare_image }}
|
|
FUNCTIONAL_IMAGE: ${{ steps.image.outputs.functional_image }}
|
|
NEEDS_BARE_IMAGE: ${{ steps.plan.outputs.needs_bare_image }}
|
|
NEEDS_FUNCTIONAL_IMAGE: ${{ steps.plan.outputs.needs_functional_image }}
|
|
PACKAGE_SHA256: ${{ steps.package.outputs.sha256 }}
|
|
SHARED_IMAGE_ARTIFACT_NAMESPACE: ${{ inputs.shared_image_artifact_namespace }}
|
|
TARGET_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
WORKFLOW_SHA: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
artifact_dir="${RUNNER_TEMP}/docker-e2e-shared-images"
|
|
artifact_name="docker-e2e-shared-images-${SHARED_IMAGE_ARTIFACT_NAMESPACE}-${TARGET_SHA:0:12}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
|
images=()
|
|
if [[ "$NEEDS_BARE_IMAGE" == "1" ]]; then
|
|
images+=("$BARE_IMAGE")
|
|
fi
|
|
if [[ "$NEEDS_FUNCTIONAL_IMAGE" == "1" ]]; then
|
|
images+=("$FUNCTIONAL_IMAGE")
|
|
fi
|
|
OPENCLAW_SHARED_IMAGE_PACKAGE_SHA256="$PACKAGE_SHA256" \
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
pack "$artifact_dir" docker-e2e "$TARGET_SHA" "$WORKFLOW_SHA" "${images[@]}"
|
|
archive_sha256="$(jq -er '.archive.sha256 | select(test("^[a-f0-9]{64}$"))' \
|
|
"$artifact_dir/shared-image-artifact.json")"
|
|
{
|
|
echo "artifact_name=$artifact_name"
|
|
echo "artifact_path=$artifact_dir"
|
|
echo "archive_sha256=$archive_sha256"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Upload Docker E2E image artifact
|
|
id: upload_image_artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact' && steps.plan.outputs.needs_e2e_image == '1'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: ${{ steps.image_artifact.outputs.artifact_name }}
|
|
path: ${{ steps.image_artifact.outputs.artifact_path }}
|
|
if-no-files-found: error
|
|
compression-level: 0
|
|
retention-days: 7
|
|
|
|
docker_e2e_image_ready:
|
|
needs: prepare_docker_e2e_image
|
|
if: always() && needs.prepare_docker_e2e_image.result != 'skipped'
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Verify Docker E2E image preparation
|
|
env:
|
|
PREPARE_RESULT: ${{ needs.prepare_docker_e2e_image.result }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "$PREPARE_RESULT" != "success" ]]; then
|
|
echo "Docker E2E image preparation ended with ${PREPARE_RESULT}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
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 || inputs.live_advisory }}
|
|
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: read
|
|
packages: read
|
|
outputs:
|
|
live_image: ${{ steps.image.outputs.live_image }}
|
|
image_exists: ${{ steps.image_exists.outputs.exists }}
|
|
image_artifact_name: ${{ steps.image_artifact.outputs.artifact_name }}
|
|
image_archive_sha256: ${{ steps.image_artifact.outputs.archive_sha256 }}
|
|
image_artifact_id: ${{ steps.upload_image_artifact.outputs.artifact-id }}
|
|
image_artifact_digest: ${{ steps.upload_image_artifact.outputs.artifact-digest }}
|
|
image_artifact_run_id: ${{ github.run_id }}
|
|
image_artifact_run_attempt: ${{ github.run_attempt }}
|
|
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
|
|
persist-credentials: false
|
|
|
|
- name: Checkout trusted release harness
|
|
if: inputs.shared_image_policy == 'no-push-artifact'
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
path: .release-harness
|
|
persist-credentials: false
|
|
|
|
- name: Resolve shared live-test image tag
|
|
id: image
|
|
shell: bash
|
|
env:
|
|
SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
SHARED_IMAGE_POLICY: ${{ inputs.shared_image_policy }}
|
|
run: |
|
|
set -euo pipefail
|
|
repository="${GITHUB_REPOSITORY,,}"
|
|
live_image_extensions="matrix,acpx"
|
|
live_image_tag_suffix="${live_image_extensions//,/-}"
|
|
if [[ "$SHARED_IMAGE_POLICY" == "no-push-artifact" ]]; then
|
|
live_image="openclaw-live-test:${SELECTED_SHA}-${live_image_tag_suffix}"
|
|
else
|
|
live_image="ghcr.io/${repository}-live-test:${SELECTED_SHA}-${live_image_tag_suffix}"
|
|
fi
|
|
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
|
|
if: inputs.shared_image_policy != 'no-push-artifact'
|
|
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
|
|
if: inputs.shared_image_policy != 'no-push-artifact'
|
|
env:
|
|
SHARED_IMAGE_POLICY: ${{ inputs.shared_image_policy }}
|
|
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
|
|
if [[ "$SHARED_IMAGE_POLICY" == "existing-only" ]]; then
|
|
echo "shared_image_policy=existing-only forbids building or pushing a missing live-test image." >&2
|
|
exit 1
|
|
fi
|
|
echo "exists=0" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Setup Docker builder
|
|
if: inputs.shared_image_policy == 'no-push-artifact'
|
|
uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1
|
|
with:
|
|
max-cache-size-mb: 800000
|
|
|
|
- name: Build shared live-test image
|
|
if: inputs.shared_image_policy == 'no-push-artifact'
|
|
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 }}
|
|
# Attestations remain on registry-published images; this path loads a local artifact.
|
|
sbom: false
|
|
provenance: false
|
|
load: true
|
|
push: false
|
|
|
|
- name: Pack live-test image artifact
|
|
id: image_artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact'
|
|
env:
|
|
LIVE_IMAGE: ${{ steps.image.outputs.live_image }}
|
|
SHARED_IMAGE_ARTIFACT_NAMESPACE: ${{ inputs.shared_image_artifact_namespace }}
|
|
TARGET_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
WORKFLOW_SHA: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
artifact_dir="${RUNNER_TEMP}/live-test-shared-image"
|
|
artifact_name="live-test-shared-image-${SHARED_IMAGE_ARTIFACT_NAMESPACE}-${TARGET_SHA:0:12}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
pack "$artifact_dir" live-test "$TARGET_SHA" "$WORKFLOW_SHA" "$LIVE_IMAGE"
|
|
archive_sha256="$(jq -er '.archive.sha256 | select(test("^[a-f0-9]{64}$"))' \
|
|
"$artifact_dir/shared-image-artifact.json")"
|
|
{
|
|
echo "artifact_name=$artifact_name"
|
|
echo "artifact_path=$artifact_dir"
|
|
echo "archive_sha256=$archive_sha256"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Upload live-test image artifact
|
|
id: upload_image_artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact'
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: ${{ steps.image_artifact.outputs.artifact_name }}
|
|
path: ${{ steps.image_artifact.outputs.artifact_path }}
|
|
if-no-files-found: error
|
|
compression-level: 0
|
|
retention-days: 7
|
|
|
|
live_test_image_ready:
|
|
needs: prepare_live_test_image
|
|
if: always() && needs.prepare_live_test_image.result != 'skipped'
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Verify live-test image preparation
|
|
env:
|
|
PREPARE_RESULT: ${{ needs.prepare_live_test_image.result }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "$PREPARE_RESULT" != "success" ]]; then
|
|
echo "Live-test image preparation ended with ${PREPARE_RESULT}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
validate_live_models_docker:
|
|
name: Docker live models (${{ matrix.provider_label }})
|
|
needs:
|
|
[
|
|
validate_selected_ref,
|
|
prepare_live_test_image,
|
|
live_test_image_ready,
|
|
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 || inputs.live_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_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
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_LIVE_REQUIRE_LOCAL_IMAGE: ${{ inputs.shared_image_policy == 'no-push-artifact' && '1' || '0' }}
|
|
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
|
|
persist-credentials: false
|
|
|
|
- name: Checkout trusted live Docker harness
|
|
if: contains(matrix.profiles, inputs.release_test_profile)
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
persist-credentials: false
|
|
|
|
- name: Validate live-test image artifact binding
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && inputs.shared_image_policy == 'no-push-artifact'
|
|
env:
|
|
ARTIFACT_DIGEST: ${{ needs.prepare_live_test_image.outputs.image_artifact_digest }}
|
|
ARTIFACT_ID: ${{ needs.prepare_live_test_image.outputs.image_artifact_id }}
|
|
ARTIFACT_NAME: ${{ needs.prepare_live_test_image.outputs.image_artifact_name }}
|
|
ARTIFACT_RUN_ATTEMPT: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_attempt }}
|
|
ARTIFACT_RUN_ID: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_id }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
verify-upload "live-test image" \
|
|
"$ARTIFACT_ID" "$ARTIFACT_NAME" "$ARTIFACT_DIGEST" \
|
|
"$ARTIFACT_RUN_ID" "$ARTIFACT_RUN_ATTEMPT"
|
|
|
|
- name: Download live-test image artifact
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && inputs.shared_image_policy == 'no-push-artifact'
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
artifact-ids: ${{ needs.prepare_live_test_image.outputs.image_artifact_id }}
|
|
path: .artifacts/live-test-image
|
|
run-id: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Verify and load live-test image artifact
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && inputs.shared_image_policy == 'no-push-artifact'
|
|
env:
|
|
ARCHIVE_SHA256: ${{ needs.prepare_live_test_image.outputs.image_archive_sha256 }}
|
|
LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ATTEMPT: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_attempt }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ID: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_id }}
|
|
TARGET_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
WORKFLOW_SHA: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
OPENCLAW_SHARED_IMAGE_ARCHIVE_SHA256="$ARCHIVE_SHA256" \
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
load .artifacts/live-test-image live-test "$TARGET_SHA" "$WORKFLOW_SHA" "$LIVE_IMAGE"
|
|
|
|
- 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) && inputs.shared_image_policy != 'no-push-artifact'
|
|
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_OAUTH_TOKEN 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, live_test_image_ready]
|
|
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 || inputs.live_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_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
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_LIVE_REQUIRE_LOCAL_IMAGE: ${{ inputs.shared_image_policy == 'no-push-artifact' && '1' || '0' }}
|
|
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
|
|
persist-credentials: false
|
|
|
|
- name: Checkout trusted live Docker harness
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
persist-credentials: false
|
|
|
|
- name: Validate live-test image artifact binding
|
|
if: inputs.shared_image_policy == 'no-push-artifact'
|
|
env:
|
|
ARTIFACT_DIGEST: ${{ needs.prepare_live_test_image.outputs.image_artifact_digest }}
|
|
ARTIFACT_ID: ${{ needs.prepare_live_test_image.outputs.image_artifact_id }}
|
|
ARTIFACT_NAME: ${{ needs.prepare_live_test_image.outputs.image_artifact_name }}
|
|
ARTIFACT_RUN_ATTEMPT: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_attempt }}
|
|
ARTIFACT_RUN_ID: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_id }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
verify-upload "live-test image" \
|
|
"$ARTIFACT_ID" "$ARTIFACT_NAME" "$ARTIFACT_DIGEST" \
|
|
"$ARTIFACT_RUN_ID" "$ARTIFACT_RUN_ATTEMPT"
|
|
|
|
- name: Download live-test image artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact'
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
artifact-ids: ${{ needs.prepare_live_test_image.outputs.image_artifact_id }}
|
|
path: .artifacts/live-test-image
|
|
run-id: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Verify and load live-test image artifact
|
|
if: inputs.shared_image_policy == 'no-push-artifact'
|
|
env:
|
|
ARCHIVE_SHA256: ${{ needs.prepare_live_test_image.outputs.image_archive_sha256 }}
|
|
LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ATTEMPT: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_attempt }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ID: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_id }}
|
|
TARGET_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
WORKFLOW_SHA: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
OPENCLAW_SHARED_IMAGE_ARCHIVE_SHA256="$ARCHIVE_SHA256" \
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
load .artifacts/live-test-image live-test "$TARGET_SHA" "$WORKFLOW_SHA" "$LIVE_IMAGE"
|
|
|
|
- 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
|
|
if: inputs.shared_image_policy != 'no-push-artifact'
|
|
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_OAUTH_TOKEN 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 || inputs.live_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.6-luna 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-openai-api-default
|
|
label: Native live gateway fresh OpenAI API-key default
|
|
command: OPENCLAW_LIVE_GATEWAY_OPENAI_API_DEFAULT=1 OPENCLAW_LIVE_GATEWAY_THINKING=off OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 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: stable full
|
|
- suite_id: native-live-src-gateway-profiles-openai-gpt56-ultra
|
|
label: Native live gateway OpenAI GPT-5.6 Ultra
|
|
command: OPENCLAW_LIVE_GATEWAY_THINKING=ultra OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.6-sol,openai/gpt-5.6-terra,openai/gpt-5.6-luna OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=300000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=900000 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
|
|
timeout_minutes: 75
|
|
profile_env_only: false
|
|
profiles: 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 OPENCLAW_LIVE_GATEWAY_MODELS=deepseek/deepseek-v4-flash,deepseek/deepseek-v4-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-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.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_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
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:
|
|
persist-credentials: false
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_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 "${OPENCLAW_CLAUDE_CREDENTIALS_JSON:-}" || -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then
|
|
echo "OPENCLAW_LIVE_ACP_BIND_CLAUDE_AUTH=subscription" >> "$GITHUB_ENV"
|
|
else
|
|
echo "OPENCLAW_LIVE_ACP_BIND_CLAUDE_AUTH=api-key" >> "$GITHUB_ENV"
|
|
fi
|
|
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, live_test_image_ready]
|
|
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-'))
|
|
continue-on-error: ${{ inputs.advisory || inputs.live_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.6-luna 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
|
|
suite_group: 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
|
|
- suite_id: live-gateway-anthropic-docker-full
|
|
suite_group: 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
|
|
advisory: true
|
|
profiles: 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-codex-harness-gpt56-sol-docker
|
|
suite_group: live-codex-harness-gpt56-docker
|
|
label: Docker live Codex GPT-5.6 Sol Ultra
|
|
command: OPENCLAW_LIVE_CODEX_HARNESS_TARGETS=openai/gpt-5.6-sol=ultra 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-codex-harness-gpt56-terra-docker
|
|
suite_group: live-codex-harness-gpt56-docker
|
|
label: Docker live Codex GPT-5.6 Terra Ultra
|
|
command: OPENCLAW_LIVE_CODEX_HARNESS_TARGETS=openai/gpt-5.6-terra=ultra 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-codex-harness-gpt56-luna-docker
|
|
suite_group: live-codex-harness-gpt56-docker
|
|
label: Docker live Codex GPT-5.6 Luna Max
|
|
command: OPENCLAW_LIVE_CODEX_HARNESS_TARGETS=openai/gpt-5.6-luna=max 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_LIVE_REQUIRE_LOCAL_IMAGE: ${{ inputs.shared_image_policy == 'no-push-artifact' && '1' || '0' }}
|
|
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 == matrix.suite_group)
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
- 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 == matrix.suite_group)
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
persist-credentials: false
|
|
|
|
- name: Validate live-test image artifact binding
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || inputs.live_suite_filter == matrix.suite_group) && inputs.shared_image_policy == 'no-push-artifact'
|
|
env:
|
|
ARTIFACT_DIGEST: ${{ needs.prepare_live_test_image.outputs.image_artifact_digest }}
|
|
ARTIFACT_ID: ${{ needs.prepare_live_test_image.outputs.image_artifact_id }}
|
|
ARTIFACT_NAME: ${{ needs.prepare_live_test_image.outputs.image_artifact_name }}
|
|
ARTIFACT_RUN_ATTEMPT: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_attempt }}
|
|
ARTIFACT_RUN_ID: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_id }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
verify-upload "live-test image" \
|
|
"$ARTIFACT_ID" "$ARTIFACT_NAME" "$ARTIFACT_DIGEST" \
|
|
"$ARTIFACT_RUN_ID" "$ARTIFACT_RUN_ATTEMPT"
|
|
|
|
- name: Download live-test image artifact
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || inputs.live_suite_filter == matrix.suite_group) && inputs.shared_image_policy == 'no-push-artifact'
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
artifact-ids: ${{ needs.prepare_live_test_image.outputs.image_artifact_id }}
|
|
path: .artifacts/live-test-image
|
|
run-id: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Verify and load live-test image artifact
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || inputs.live_suite_filter == matrix.suite_group) && inputs.shared_image_policy == 'no-push-artifact'
|
|
env:
|
|
ARCHIVE_SHA256: ${{ needs.prepare_live_test_image.outputs.image_archive_sha256 }}
|
|
LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ATTEMPT: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_attempt }}
|
|
OPENCLAW_SHARED_IMAGE_RUN_ID: ${{ needs.prepare_live_test_image.outputs.image_artifact_run_id }}
|
|
TARGET_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
WORKFLOW_SHA: ${{ needs.validate_selected_ref.outputs.workflow_sha }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
OPENCLAW_SHARED_IMAGE_ARCHIVE_SHA256="$ARCHIVE_SHA256" \
|
|
bash .release-harness/scripts/docker/shared-image-artifact.sh \
|
|
load .artifacts/live-test-image live-test "$TARGET_SHA" "$WORKFLOW_SHA" "$LIVE_IMAGE"
|
|
|
|
- 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 == matrix.suite_group)
|
|
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 == matrix.suite_group)
|
|
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 == matrix.suite_group) && inputs.shared_image_policy != 'no-push-artifact'
|
|
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 == matrix.suite_group)
|
|
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 "${OPENCLAW_CLAUDE_CREDENTIALS_JSON:-}" || -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then
|
|
echo "OPENCLAW_LIVE_ACP_BIND_CLAUDE_AUTH=subscription" >> "$GITHUB_ENV"
|
|
else
|
|
echo "OPENCLAW_LIVE_ACP_BIND_CLAUDE_AUTH=api-key" >> "$GITHUB_ENV"
|
|
fi
|
|
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 == matrix.suite_group)
|
|
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 || inputs.live_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:
|
|
persist-credentials: false
|
|
repository: ${{ needs.validate_selected_ref.outputs.workflow_repository }}
|
|
ref: ${{ needs.validate_selected_ref.outputs.workflow_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"
|