CI: expand native release validation coverage (#67144)

* Actions: grant reusable release checks actions read

* Actions: use read-all for reusable release checks

* CI: add native cross-OS release checks

* CI: wire Discord smoke secrets for cross-OS checks

* CI: fix native cross-OS installer compatibility

* CI: skip empty pnpm cache saves in matrix jobs

* CI: honor workflow runner override envs

* CI: finish native cross-OS update checks

* CI: fix native cross-OS workflow regressions

* Installer: capture Windows npm stderr safely

* CI: harden cross-OS release checks

* CI: resolve reusable workflow harness ref

* CI: stabilize cross-OS dev update lanes

* CI: tighten release-check workflow semantics

* CI: repoint repaired git CLI on POSIX

* CI: repair native dev-update shell handoff

* CI: preserve real updater semantics

* CI: harden supported release-check refs

* CI: harden release-check refs and fresh mode

* CI: skip dev-update for immutable tag refs

* CI: repair fresh installer release checks

* CI: fix native release check installer lanes

* CI: install release checks from candidate artifacts

* CI: use Windows cmd shims in release checks

* Installer: run Windows npm shim via PowerShell

* CI: pin dev update verification to candidate sha

* CI: pin reusable harness and published installers

* CI: isolate Windows dev-update PATH validation

* CI: align Windows dev-update bootstrap validation

* CI: avoid Windows installer gateway flake

* CI: run cross-OS release checks via TypeScript

* CI: bootstrap tsx for release-check workflow

* CI: fix native release-check follow-ups

* CI: tighten dev-update release checks

* CI: peel annotated workflow refs

* CI: harden native release checks

* CI: fix release-check verifier drift

* CI: fix release-check workflow drift

* CI: fix release-check ref resolution

* CI: harden Windows release-check gateway startup

* CI: fix release-check fallback validation

* CI: harden cross-os release checks

* CI: pin dev-update release checks to candidate SHA

* CI: resolve remote dev target refs

* CI: detect cloned dev-update checkouts

* CI: harden Windows release-check launcher

* Windows: harden task fallback and runner overrides

* Release checks: preserve Windows PATH and baseline version reads

* CI: add release validation live lanes

* CI: expand live and e2e release coverage

* CI: add branch dispatch for live and e2e checks
This commit is contained in:
Onur
2026-04-16 19:58:19 +02:00
committed by GitHub
parent 687ede50a5
commit 900e291f31
17 changed files with 5457 additions and 206 deletions

View File

@@ -1,12 +1,67 @@
name: OpenClaw Cross-OS Release Checks (Reusable)
on:
workflow_dispatch:
inputs:
ref:
description: Public OpenClaw ref to validate (tag, branch, or full commit SHA)
required: true
default: main
type: string
workflow_ref:
description: Optional openclaw/openclaw ref that provides the reusable workflow harness
required: false
default: ""
type: string
provider:
description: Provider lane to use for onboarding and the end-to-end turn
required: true
default: openai
type: choice
options:
- openai
- anthropic
- minimax
mode:
description: Which release-check lanes to run
required: true
default: both
type: choice
options:
- fresh
- upgrade
- both
previous_version:
description: Optional baseline version for installer/dev-update and packaged upgrade
required: false
default: ""
type: string
ubuntu_runner:
description: Optional Linux runner label override
required: false
default: ""
type: string
windows_runner:
description: Optional Windows runner label override
required: false
default: ""
type: string
macos_runner:
description: Optional macOS runner label override
required: false
default: ""
type: string
workflow_call:
inputs:
ref:
description: Public OpenClaw ref to validate (tag, branch, or full commit SHA)
required: true
type: string
workflow_ref:
description: Optional openclaw/openclaw ref that provides the reusable workflow harness
required: false
default: ""
type: string
provider:
description: Provider lane to use for onboarding and the end-to-end turn
required: true
@@ -42,6 +97,14 @@ on:
required: false
MINIMAX_API_KEY:
required: false
OPENCLAW_DISCORD_SMOKE_BOT_TOKEN:
required: false
OPENCLAW_DISCORD_SMOKE_GUILD_ID:
required: false
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID:
required: false
permissions: read-all
concurrency:
group: openclaw-cross-os-release-checks-${{ inputs.ref }}-${{ inputs.provider }}-${{ inputs.mode }}
@@ -52,12 +115,11 @@ env:
NODE_VERSION: "24.x"
PNPM_VERSION: "10.32.1"
OPENCLAW_REPOSITORY: openclaw/openclaw
TSX_VERSION: "4.21.0"
jobs:
prepare:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
baseline_file_name: ${{ steps.baseline_metadata.outputs.file_name }}
baseline_spec: ${{ steps.baseline.outputs.value }}
@@ -65,6 +127,7 @@ jobs:
candidate_version: ${{ steps.candidate_metadata.outputs.version }}
matrix: ${{ steps.matrix.outputs.value }}
source_sha: ${{ steps.candidate_metadata.outputs.source_sha }}
workflow_ref: ${{ steps.workflow_ref.outputs.value }}
steps:
- name: Validate provider secret availability
env:
@@ -90,9 +153,109 @@ jobs:
;;
esac
- name: Checkout caller release workflow repo
- name: Resolve workflow ref
id: workflow_ref
env:
INPUT_WORKFLOW_REF: ${{ inputs.workflow_ref }}
CALLER_REPOSITORY: ${{ github.repository }}
CURRENT_SHA: ${{ github.sha }}
WORKFLOW_CONTEXT_REF: ${{ github.workflow_ref }}
WORKFLOW_REPOSITORY: ${{ env.OPENCLAW_REPOSITORY }}
run: |
set -euo pipefail
resolve_unique_remote_ref() {
local remote_url="$1"
shift
local -a refs=("$@")
local -a matches=()
local ref=""
for ref in "${refs[@]}"; do
[[ -n "${ref}" ]] || continue
mapfile -t matches < <(
git ls-remote "${remote_url}" "${ref}" | awk '{print $1}' | awk '!seen[$0]++'
)
if [[ "${#matches[@]}" -eq 0 ]]; then
continue
fi
if [[ "${#matches[@]}" -ne 1 ]]; then
return 2
fi
printf '%s\n' "${matches[0]}"
return 0
done
return 1
}
if [[ -n "${INPUT_WORKFLOW_REF}" ]]; then
TARGET_REF="${INPUT_WORKFLOW_REF}"
elif [[ "${CALLER_REPOSITORY}" == "${WORKFLOW_REPOSITORY}" ]]; then
TARGET_REF="${CURRENT_SHA}"
elif [[ "${WORKFLOW_CONTEXT_REF}" == "${WORKFLOW_REPOSITORY}/"* ]] && [[ "${WORKFLOW_CONTEXT_REF}" == *"@"* ]]; then
TARGET_REF="${WORKFLOW_CONTEXT_REF##*@}"
else
echo "Failed to infer workflow ref from github.workflow_ref=${WORKFLOW_CONTEXT_REF}" >&2
exit 1
fi
if [[ "${TARGET_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "value=${TARGET_REF}" >> "$GITHUB_OUTPUT"
exit 0
fi
REMOTE_URL="https://github.com/${WORKFLOW_REPOSITORY}.git"
if [[ "${TARGET_REF}" == refs/* ]]; then
if [[ "${TARGET_REF}" == refs/tags/* ]]; then
mapfile -t MATCHES < <(
resolve_unique_remote_ref "${REMOTE_URL}" "${TARGET_REF}^{}" "${TARGET_REF}" || true
)
else
mapfile -t MATCHES < <(resolve_unique_remote_ref "${REMOTE_URL}" "${TARGET_REF}" || true)
fi
else
mapfile -t BRANCH_MATCHES < <(
resolve_unique_remote_ref "${REMOTE_URL}" "refs/heads/${TARGET_REF}" || true
)
mapfile -t TAG_MATCHES < <(
resolve_unique_remote_ref "${REMOTE_URL}" "refs/tags/${TARGET_REF}^{}" "refs/tags/${TARGET_REF}" || true
)
MATCH_COUNT=$(( ${#BRANCH_MATCHES[@]} + ${#TAG_MATCHES[@]} ))
if [[ "${MATCH_COUNT}" -eq 1 ]]; then
if [[ "${#BRANCH_MATCHES[@]}" -eq 1 ]]; then
MATCHES=("${BRANCH_MATCHES[0]}")
else
MATCHES=("${TAG_MATCHES[0]}")
fi
elif [[ "${MATCH_COUNT}" -eq 0 ]]; then
MATCHES=()
else
echo "Workflow ref resolved ambiguously: ${TARGET_REF}" >&2
exit 1
fi
fi
case "${#MATCHES[@]}" in
1)
echo "value=${MATCHES[0]}" >> "$GITHUB_OUTPUT"
;;
0)
echo "Failed to resolve workflow ref: ${TARGET_REF}" >&2
exit 1
;;
*)
echo "Workflow ref resolved ambiguously: ${TARGET_REF}" >&2
exit 1
;;
esac
- name: Checkout workflow repo
uses: actions/checkout@v6
with:
repository: ${{ env.OPENCLAW_REPOSITORY }}
ref: ${{ steps.workflow_ref.outputs.value }}
path: workflow
fetch-depth: 1
persist-credentials: false
@@ -123,7 +286,7 @@ jobs:
env:
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare
run: |
node --disable-warning=ExperimentalWarning scripts/openclaw-cross-os-release-checks.ts \
pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
--prepare-only \
--source-dir source \
--output-dir "${OUTPUT_DIR}"
@@ -198,6 +361,7 @@ jobs:
- name: Resolve runner matrix
id: matrix
env:
INPUT_REF: ${{ inputs.ref }}
INPUT_MODE: ${{ inputs.mode }}
INPUT_UBUNTU_RUNNER: ${{ inputs.ubuntu_runner }}
INPUT_WINDOWS_RUNNER: ${{ inputs.windows_runner }}
@@ -206,53 +370,30 @@ jobs:
VAR_WINDOWS_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_WINDOWS_RUNNER }}
VAR_MACOS_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_MACOS_RUNNER }}
run: |
node <<'NODE' >>"$GITHUB_OUTPUT"
const pick = (...values) => values.find((value) => typeof value === "string" && value.trim().length > 0)?.trim();
const lanes = (process.env.INPUT_MODE ?? "both") === "both" ? ["fresh", "upgrade"] : [process.env.INPUT_MODE ?? "both"];
const runners = [
{
os_id: "ubuntu",
display_name: "Linux",
runner: pick(process.env.INPUT_UBUNTU_RUNNER, process.env.VAR_UBUNTU_RUNNER, "ubuntu-latest"),
artifact_name: "linux",
},
{
os_id: "windows",
display_name: "Windows",
runner: pick(
process.env.INPUT_WINDOWS_RUNNER,
process.env.VAR_WINDOWS_RUNNER,
"blacksmith-32vcpu-windows-2025",
),
artifact_name: "windows",
},
{
os_id: "macos",
display_name: "macOS",
runner: pick(process.env.INPUT_MACOS_RUNNER, process.env.VAR_MACOS_RUNNER, "macos-latest-xlarge"),
artifact_name: "macos",
},
];
const matrix = {
include: runners.flatMap((runner) => lanes.map((lane) => ({ ...runner, lane }))),
};
process.stdout.write(`value=${JSON.stringify(matrix)}\n`);
NODE
MATRIX_JSON="$(pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
--resolve-matrix \
--ref "${INPUT_REF}" \
--mode "${INPUT_MODE}" \
--ubuntu-runner "${INPUT_UBUNTU_RUNNER}" \
--windows-runner "${INPUT_WINDOWS_RUNNER}" \
--macos-runner "${INPUT_MACOS_RUNNER}")"
echo "value=${MATRIX_JSON}" >> "$GITHUB_OUTPUT"
cross_os_release_checks:
name: "${{ matrix.display_name }} / ${{ matrix.lane }}"
name: "${{ matrix.display_name }} / ${{ matrix.suite_label }}"
needs: prepare
permissions:
contents: read
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
steps:
- name: Checkout caller release workflow repo
- name: Checkout workflow repo
uses: actions/checkout@v6
with:
repository: ${{ env.OPENCLAW_REPOSITORY }}
ref: ${{ needs.prepare.outputs.workflow_ref }}
path: workflow
fetch-depth: 1
persist-credentials: false
@@ -274,7 +415,7 @@ jobs:
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate
- name: Download baseline artifact
if: ${{ matrix.lane == 'upgrade' }}
if: ${{ matrix.suite == 'packaged-upgrade' }}
uses: actions/download-artifact@v8
with:
name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }}
@@ -286,24 +427,35 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
OPENCLAW_DISCORD_SMOKE_BOT_TOKEN: ${{ secrets.OPENCLAW_DISCORD_SMOKE_BOT_TOKEN }}
OPENCLAW_DISCORD_SMOKE_GUILD_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_GUILD_ID }}
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_CHANNEL_ID }}
OPENCLAW_RELEASE_CHECK_OS: ${{ matrix.os_id }}
OPENCLAW_RELEASE_CHECK_RUNNER: ${{ matrix.runner }}
run: |
node --disable-warning=ExperimentalWarning scripts/openclaw-cross-os-release-checks.ts \
DISCORD_ARGS=()
if [[ -n "${OPENCLAW_DISCORD_SMOKE_BOT_TOKEN}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_GUILD_ID}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_CHANNEL_ID}" ]]; then
DISCORD_ARGS+=(--run-discord-roundtrip true)
fi
pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
--candidate-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}" \
--candidate-version "${{ needs.prepare.outputs.candidate_version }}" \
--source-sha "${{ needs.prepare.outputs.source_sha }}" \
--baseline-spec "${{ needs.prepare.outputs.baseline_spec }}" \
--previous-version "${{ inputs.previous_version }}" \
--baseline-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}" \
--provider "${{ inputs.provider }}" \
--mode "${{ matrix.lane }}" \
--output-dir "$RUNNER_TEMP/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.lane }}"
--suite "${{ matrix.suite }}" \
--ref "${{ inputs.ref }}" \
"${DISCORD_ARGS[@]}" \
--output-dir "$RUNNER_TEMP/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}"
- name: Summarize release checks
if: always()
shell: bash
env:
SUMMARY_PATH: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.lane }}/summary.md
SUMMARY_PATH: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}/summary.md
run: |
if [[ -f "${SUMMARY_PATH}" ]]; then
cat "${SUMMARY_PATH}" >> "$GITHUB_STEP_SUMMARY"
@@ -315,6 +467,6 @@ jobs:
if: always()
uses: actions/upload-artifact@v7
with:
name: openclaw-cross-os-release-checks-${{ matrix.artifact_name }}-${{ matrix.lane }}-${{ github.run_id }}
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.lane }}
name: openclaw-cross-os-release-checks-${{ matrix.artifact_name }}-${{ matrix.suite }}-${{ github.run_id }}
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
if-no-files-found: error

View File

@@ -0,0 +1,563 @@
name: OpenClaw Live And E2E Checks (Reusable)
on:
workflow_dispatch:
inputs:
ref:
description: Ref, tag, or SHA to validate
required: true
default: main
type: string
include_repo_e2e:
description: Whether to run pnpm test:e2e plus repo-specific extra E2E lanes
required: false
default: true
type: boolean
include_release_path_suites:
description: Whether to run the Docker release-path suites
required: false
default: true
type: boolean
include_openwebui:
description: Whether to run the Open WebUI Docker smoke
required: false
default: true
type: boolean
include_live_suites:
description: Whether to run live-provider coverage
required: false
default: true
type: boolean
workflow_call:
inputs:
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
include_live_suites:
description: Whether to run live-provider coverage
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
BYTEPLUS_API_KEY:
required: false
CEREBRAS_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
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.32.1"
jobs:
validate_release_live_cache:
if: inputs.include_live_suites
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 60
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@v6
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- 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: pnpm test:live:cache
validate_repo_e2e:
if: inputs.include_repo_e2e
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 90
env:
OPENCLAW_VITEST_MAX_WORKERS: "2"
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Run repo E2E suite
run: pnpm test:e2e
validate_special_e2e:
if: inputs.include_repo_e2e || inputs.include_live_suites
runs-on: 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: 120
requires_repo_e2e: true
requires_live_suites: false
- suite_id: openai-ws-stream-live-e2e
label: OpenAI WebSocket live E2E
command: pnpm test:e2e -- src/agents/openai-ws-stream.e2e.test.ts
timeout_minutes: 90
requires_repo_e2e: false
requires_live_suites: true
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENCLAW_E2E_WORKERS: "1"
OPENCLAW_VITEST_MAX_WORKERS: "1"
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Configure suite-specific env
shell: bash
run: |
set -euo pipefail
case "${{ matrix.suite_id }}" in
openai-ws-stream-live-e2e)
echo "OPENAI_LIVE_TEST=1" >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_TEST=1" >> "$GITHUB_ENV"
;;
esac
- name: Validate suite credentials
shell: bash
run: |
set -euo pipefail
case "${{ matrix.suite_id }}" in
openai-ws-stream-live-e2e)
[[ -n "${OPENAI_API_KEY:-}" ]] || {
echo "OPENAI_API_KEY is required for the OpenAI WebSocket live E2E suite." >&2
exit 1
}
;;
esac
- name: Run ${{ matrix.label }}
if: |
(inputs.include_repo_e2e && matrix.requires_repo_e2e) ||
(inputs.include_live_suites && matrix.requires_live_suites)
run: ${{ matrix.command }}
validate_docker_e2e:
if: inputs.include_release_path_suites || inputs.include_openwebui
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
matrix:
include:
- suite_id: docker-onboard
label: Onboarding Docker E2E
command: pnpm test:docker:onboard
timeout_minutes: 60
release_path: true
openwebui_only: false
- suite_id: docker-gateway-network
label: Gateway Network Docker E2E
command: pnpm test:docker:gateway-network
timeout_minutes: 60
release_path: true
openwebui_only: false
- suite_id: docker-mcp-channels
label: MCP Channels Docker E2E
command: pnpm test:docker:mcp-channels
timeout_minutes: 60
release_path: true
openwebui_only: false
- suite_id: docker-plugins
label: Plugins Docker E2E
command: pnpm test:docker:plugins
timeout_minutes: 75
release_path: true
openwebui_only: false
- suite_id: docker-doctor-switch
label: Doctor Install Switch Docker E2E
command: pnpm test:docker:doctor-switch
timeout_minutes: 60
release_path: true
openwebui_only: false
- suite_id: docker-qr
label: QR Import Docker E2E
command: pnpm test:docker:qr
timeout_minutes: 60
release_path: true
openwebui_only: false
- suite_id: docker-install-e2e
label: Installer Docker E2E
command: pnpm test:install:e2e
timeout_minutes: 120
release_path: true
openwebui_only: false
- suite_id: docker-openwebui
label: Open WebUI Docker E2E
command: pnpm test:docker:openwebui
timeout_minutes: 75
release_path: false
openwebui_only: true
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 }}
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 }}
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Hydrate live auth/profile inputs
run: bash scripts/ci-hydrate-live-auth.sh
- name: Configure suite-specific env
shell: bash
run: |
set -euo pipefail
case "${{ matrix.suite_id }}" in
docker-install-e2e)
echo "OPENCLAW_E2E_MODELS=both" >> "$GITHUB_ENV"
;;
esac
- name: Validate suite credentials
shell: bash
run: |
set -euo pipefail
case "${{ matrix.suite_id }}" in
docker-install-e2e)
[[ -n "${OPENAI_API_KEY:-}" ]] || {
echo "OPENAI_API_KEY is required for installer Docker E2E." >&2
exit 1
}
if [[ -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for installer Docker E2E." >&2
exit 1
fi
;;
docker-openwebui)
[[ -n "${OPENAI_API_KEY:-}" ]] || {
echo "OPENAI_API_KEY is required for the Open WebUI Docker smoke." >&2
exit 1
}
;;
esac
- name: Run ${{ matrix.label }}
if: |
(inputs.include_release_path_suites && matrix.release_path) ||
(inputs.include_openwebui && matrix.openwebui_only)
run: ${{ matrix.command }}
validate_live_provider_suites:
if: inputs.include_live_suites
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
matrix:
include:
- suite_id: live-all
label: pnpm test:live
command: pnpm test:live
timeout_minutes: 180
profile_env_only: false
- suite_id: live-models-docker
label: Docker live models
command: pnpm test:docker:live-models
timeout_minutes: 120
profile_env_only: true
- suite_id: live-gateway-docker
label: Docker live gateway
command: pnpm test:docker:live-gateway
timeout_minutes: 120
profile_env_only: true
- suite_id: live-cli-backend-docker
label: Docker live CLI backend
command: pnpm test:docker:live-cli-backend
timeout_minutes: 120
profile_env_only: false
- suite_id: live-acp-bind-docker
label: Docker live ACP bind
command: pnpm test:docker:live-acp-bind
timeout_minutes: 120
profile_env_only: false
- suite_id: live-codex-harness-docker
label: Docker live Codex harness
command: pnpm test:docker:live-codex-harness
timeout_minutes: 120
profile_env_only: false
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 }}
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 }}
OPENCLAW_LIVE_VIDEO_GENERATION_SKIP_PROVIDERS: ""
OPENCLAW_LIVE_VYDRA_VIDEO: "1"
OPENCLAW_VITEST_MAX_WORKERS: "2"
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Hydrate live auth/profile inputs
run: bash scripts/ci-hydrate-live-auth.sh
- name: Configure suite-specific env
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=codex-cli/gpt-5.4" >> "$GITHUB_ENV"
;;
live-acp-bind-docker)
echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex,gemini" >> "$GITHUB_ENV"
;;
esac
- name: Run ${{ matrix.label }}
run: ${{ matrix.command }}

View File

@@ -7,6 +7,24 @@ on:
description: Existing release tag or current full 40-character main commit SHA to validate (for example v2026.4.12 or 0123456789abcdef0123456789abcdef01234567)
required: true
type: string
provider:
description: Provider lane for cross-OS onboarding and the end-to-end agent turn
required: false
default: openai
type: choice
options:
- openai
- anthropic
- minimax
mode:
description: Which cross-OS release lanes to run
required: false
default: both
type: choice
options:
- fresh
- upgrade
- both
concurrency:
group: openclaw-release-checks-${{ inputs.ref }}
@@ -14,18 +32,18 @@ concurrency:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.32.1"
jobs:
# THIS WORKFLOW EXISTS SO RELEASE-TIME LIVE CHECKS CAN RUN WITHOUT BLOCKING npm PUBLISH.
# PUT THE SLOWER, EXTERNAL, OR SOMETIMES-FLAKY RELEASE CHECKS HERE INSTEAD OF
# RECOUPLING THEM TO openclaw-npm-release.yml.
validate_release_live_cache:
resolve_target:
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 60
timeout-minutes: 30
permissions:
contents: read
outputs:
ref: ${{ steps.inputs.outputs.ref }}
sha: ${{ steps.ref.outputs.sha }}
provider: ${{ steps.inputs.outputs.provider }}
mode: ${{ steps.inputs.outputs.mode }}
steps:
- name: Require main workflow ref for release checks
env:
@@ -73,48 +91,53 @@ jobs:
git merge-base --is-ancestor HEAD origin/main
fi
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Validate live cache credentials
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Capture selected inputs
id: inputs
run: |
set -euo pipefail
if [[ -z "${OPENAI_API_KEY}" ]]; then
echo "Missing OPENAI_API_KEY secret for release checks." >&2
exit 1
fi
if [[ -z "${ANTHROPIC_API_KEY}" ]]; then
echo "Missing ANTHROPIC_API_KEY secret for release checks." >&2
exit 1
fi
# KEEP RELEASE-TIME LIVE COVERAGE HERE SO OPERATORS CAN RUN IT ON DEMAND
# WITHOUT MAKING THE PUBLISH PATH WAIT FOR A SLOW OR FLAKY EXTERNAL CHECK.
- name: Verify live prompt cache floors
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENCLAW_LIVE_CACHE_TEST: "1"
OPENCLAW_LIVE_TEST: "1"
run: pnpm test:live:cache
{
echo "ref=${{ inputs.ref }}"
echo "provider=${{ inputs.provider }}"
echo "mode=${{ inputs.mode }}"
} >> "$GITHUB_OUTPUT"
- name: Summarize validated ref
env:
RELEASE_REF: ${{ inputs.ref }}
RELEASE_SHA: ${{ steps.ref.outputs.sha }}
RELEASE_PROVIDER: ${{ inputs.provider }}
RELEASE_MODE: ${{ inputs.mode }}
run: |
{
echo "## Release checks"
echo
echo "- Requested ref: \`${RELEASE_REF}\`"
echo "- Validated SHA: \`${RELEASE_SHA}\`"
echo "- Check: \`pnpm test:live:cache\`"
echo "- Cross-OS provider: \`${RELEASE_PROVIDER}\`"
echo "- Cross-OS mode: \`${RELEASE_MODE}\`"
echo "- This run will execute cross-OS release validation plus the non-Parallels Docker/live/openwebui coverage from the CI migration plan."
} >> "$GITHUB_STEP_SUMMARY"
cross_os_release_checks:
needs: [resolve_target]
permissions:
contents: read
uses: ./.github/workflows/openclaw-cross-os-release-checks-reusable.yml
with:
ref: ${{ needs.resolve_target.outputs.ref }}
provider: ${{ needs.resolve_target.outputs.provider }}
mode: ${{ needs.resolve_target.outputs.mode }}
secrets: inherit
live_and_e2e_release_checks:
needs: [resolve_target]
permissions:
contents: read
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
with:
ref: ${{ needs.resolve_target.outputs.ref }}
include_repo_e2e: true
include_release_path_suites: true
include_openwebui: true
include_live_suites: true
secrets: inherit

View File

@@ -0,0 +1,29 @@
name: OpenClaw Scheduled Live And E2E Checks
on:
schedule:
- cron: "23 4 * * *"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: openclaw-scheduled-live-checks-${{ github.ref }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
live_and_openwebui_checks:
permissions:
contents: read
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
with:
ref: ${{ github.sha }}
include_repo_e2e: true
include_release_path_suites: false
include_openwebui: true
include_live_suites: true
secrets: inherit