ci: speed up release validation

This commit is contained in:
Peter Steinberger
2026-04-28 03:51:56 +01:00
parent f549703bed
commit e5452a9c57
7 changed files with 267 additions and 23 deletions

View File

@@ -26,6 +26,15 @@ on:
- fresh
- upgrade
- both
release_profile:
description: Release coverage profile for live/Docker/provider breadth
required: false
default: full
type: choice
options:
- minimum
- stable
- full
rerun_group:
description: Validation group to run
required: false
@@ -239,6 +248,7 @@ jobs:
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
PROVIDER: ${{ inputs.provider }}
MODE: ${{ inputs.mode }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RERUN_GROUP: ${{ inputs.rerun_group }}
run: |
set -euo pipefail
@@ -304,6 +314,7 @@ jobs:
echo "- Target SHA: \`${TARGET_SHA}\`"
echo "- Provider: \`${PROVIDER}\`"
echo "- Cross-OS mode: \`${MODE}\`"
echo "- Release profile: \`${RELEASE_PROFILE}\`"
echo "- Rerun group: \`${RERUN_GROUP}\`"
} >> "$GITHUB_STEP_SUMMARY"
@@ -316,6 +327,7 @@ jobs:
-f ref="$TARGET_SHA" \
-f provider="$PROVIDER" \
-f mode="$MODE" \
-f release_profile="$RELEASE_PROFILE" \
-f rerun_group="$child_rerun_group"
npm_telegram:
@@ -490,6 +502,34 @@ jobs:
fi
}
summarize_child_timing() {
local label="$1"
local run_id="$2"
if [[ -z "${run_id// }" ]]; then
return 0
fi
{
echo
echo "### Slowest jobs: ${label}"
echo
gh run view "$run_id" --json jobs --jq '
def ts: fromdateiso8601;
"| Job | Result | Minutes |",
"| --- | --- | ---: |",
([.jobs[]
| select(.startedAt != "0001-01-01T00:00:00Z" and .completedAt != "0001-01-01T00:00:00Z")
| . + {durationMin: ((((.completedAt | ts) - (.startedAt | ts)) / 60) * 10 | round / 10)}
| {name, conclusion, durationMin}]
| sort_by(.durationMin)
| reverse
| .[0:10]
| map("| `" + (.name | gsub("\\|"; "\\|")) + "` | `" + ((.conclusion // "") | tostring) + "` | " + (.durationMin | tostring) + " |")
| .[])
' || echo "_Unable to summarize jobs for run ${run_id}._"
} >> "$GITHUB_STEP_SUMMARY"
}
failed=0
if [[ "$NORMAL_CI_RESULT" == "skipped" && -z "${NORMAL_CI_RUN_ID// }" ]]; then
@@ -510,4 +550,8 @@ jobs:
check_child "npm_telegram" "$NPM_TELEGRAM_RUN_ID" 1 || failed=1
fi
summarize_child_timing "normal_ci" "$NORMAL_CI_RUN_ID"
summarize_child_timing "release_checks" "$RELEASE_CHECKS_RUN_ID"
summarize_child_timing "npm_telegram" "$NPM_TELEGRAM_RUN_ID"
exit "$failed"

View File

@@ -63,6 +63,15 @@ on:
required: false
default: ""
type: string
release_test_profile:
description: Release coverage profile for live/Docker/provider breadth
required: false
default: full
type: choice
options:
- minimum
- stable
- full
workflow_call:
inputs:
ref:
@@ -124,6 +133,11 @@ on:
required: false
default: ""
type: string
release_test_profile:
description: Release coverage profile for live/Docker/provider breadth
required: false
default: full
type: string
secrets:
OPENAI_API_KEY:
required: false
@@ -1208,22 +1222,31 @@ jobs:
include:
- provider_label: Anthropic
providers: anthropic
profiles: stable full
- provider_label: Google
providers: google
profiles: stable full
- provider_label: MiniMax
providers: minimax
profiles: stable full
- provider_label: OpenAI
providers: openai
profiles: minimum stable full
- provider_label: OpenCode
providers: opencode-go
profiles: full
- provider_label: OpenRouter
providers: openrouter
profiles: full
- provider_label: xAI
providers: xai
profiles: full
- provider_label: Z.ai
providers: zai
profiles: full
- provider_label: Fireworks
providers: fireworks
profiles: full
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
@@ -1261,12 +1284,14 @@ jobs:
OPENCLAW_VITEST_MAX_WORKERS: "2"
steps:
- name: Checkout selected ref
if: contains(matrix.profiles, inputs.release_test_profile)
uses: actions/checkout@v6
with:
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 1
- name: Setup Node environment
if: contains(matrix.profiles, inputs.release_test_profile)
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
@@ -1274,9 +1299,11 @@ jobs:
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: Validate provider credential
if: contains(matrix.profiles, inputs.release_test_profile)
shell: bash
run: |
set -euo pipefail
@@ -1311,6 +1338,7 @@ jobs:
esac
- name: Run Docker live model sweep
if: contains(matrix.profiles, inputs.release_test_profile)
run: pnpm test:docker:live-models
validate_live_models_docker_targeted:
@@ -1485,156 +1513,196 @@ jobs:
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: stable full
- suite_id: native-live-src-gateway-core
label: Native live gateway core
command: node scripts/test-live-shard.mjs native-live-src-gateway-core
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: minimum stable full
- suite_id: native-live-src-gateway-profiles-anthropic
label: Native live gateway profiles Anthropic
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic node scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: stable full
- suite_id: native-live-src-gateway-profiles-google
label: Native live gateway profiles Google
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=google node scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
needs_ffmpeg: false
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 node scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
needs_ffmpeg: false
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_PROVIDERS=openai node scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: minimum stable full
- suite_id: native-live-src-gateway-profiles-fireworks
label: Native live gateway profiles Fireworks
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=fireworks node scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-deepseek
label: Native live gateway profiles DeepSeek
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek node scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-opencode-go
label: Native live gateway profiles OpenCode Go
label: Native live gateway profiles OpenCode Go deep
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go node scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
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 scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 45
needs_ffmpeg: false
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 scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-xai
label: Native live gateway profiles xAI
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai node scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-zai
label: Native live gateway profiles Z.ai
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=zai node scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-backends
label: Native live gateway backends
command: node scripts/test-live-shard.mjs native-live-src-gateway-backends
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: stable full
- suite_id: native-live-test
label: Native live test harnesses
command: node scripts/test-live-shard.mjs native-live-test
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: stable full
- suite_id: native-live-extensions-a-k
label: Native live plugins A-K
command: node scripts/test-live-shard.mjs native-live-extensions-a-k
timeout_minutes: 90
needs_ffmpeg: true
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-l-n
label: Native live plugins L-N
command: node scripts/test-live-shard.mjs native-live-extensions-l-n
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-openai
label: Native live OpenAI plugin
command: node scripts/test-live-shard.mjs native-live-extensions-openai
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: minimum stable full
- suite_id: native-live-extensions-o-z-other
label: Native live plugins O-Z other
command: node scripts/test-live-shard.mjs native-live-extensions-o-z-other
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-xai
label: Native live xAI plugin
command: node scripts/test-live-shard.mjs native-live-extensions-xai
timeout_minutes: 90
needs_ffmpeg: false
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-media-audio
label: Native live media audio plugins
command: node scripts/test-live-shard.mjs native-live-extensions-media-audio
timeout_minutes: 90
needs_ffmpeg: true
profile_env_only: false
- suite_id: native-live-extensions-media-music
label: Native live media music plugins
command: node scripts/test-live-shard.mjs native-live-extensions-media-music
profiles: full
- suite_id: native-live-extensions-media-music-google
label: Native live media music Google
command: OPENCLAW_LIVE_MUSIC_GENERATION_PROVIDERS=google node scripts/test-live-shard.mjs native-live-extensions-media-music-google
timeout_minutes: 90
needs_ffmpeg: true
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-media-music-minimax
label: Native live media music MiniMax
command: OPENCLAW_LIVE_MUSIC_GENERATION_PROVIDERS=minimax node scripts/test-live-shard.mjs native-live-extensions-media-music-minimax
timeout_minutes: 90
needs_ffmpeg: true
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-media-video
label: Native live media video plugins
command: node scripts/test-live-shard.mjs native-live-extensions-media-video
timeout_minutes: 90
needs_ffmpeg: true
profile_env_only: false
profiles: full
- suite_id: live-gateway-docker
label: Docker live gateway
command: pnpm test:docker:live-gateway
timeout_minutes: 120
needs_ffmpeg: false
profile_env_only: false
profiles: minimum stable full
- suite_id: live-cli-backend-docker
label: Docker live CLI backend
command: pnpm test:docker:live-cli-backend
timeout_minutes: 120
needs_ffmpeg: false
profile_env_only: false
profiles: stable full
- suite_id: live-acp-bind-docker
label: Docker live ACP bind
command: pnpm test:docker:live-acp-bind
timeout_minutes: 120
needs_ffmpeg: false
profile_env_only: false
profiles: stable full
- suite_id: live-codex-harness-docker
label: Docker live Codex harness
command: pnpm test:docker:live-codex-harness
timeout_minutes: 120
needs_ffmpeg: false
profile_env_only: false
profiles: stable full
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
@@ -1685,12 +1753,14 @@ jobs:
OPENCLAW_VITEST_MAX_WORKERS: "2"
steps:
- name: Checkout selected ref
if: contains(matrix.profiles, inputs.release_test_profile)
uses: actions/checkout@v6
with:
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 1
- name: Setup Node environment
if: contains(matrix.profiles, inputs.release_test_profile)
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
@@ -1698,10 +1768,11 @@ jobs:
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: Install live media dependencies
if: matrix.needs_ffmpeg
if: matrix.needs_ffmpeg && contains(matrix.profiles, inputs.release_test_profile)
shell: bash
run: |
set -euo pipefail
@@ -1720,6 +1791,7 @@ jobs:
ffmpeg -version | head -1
- name: Configure suite-specific env
if: contains(matrix.profiles, inputs.release_test_profile)
shell: bash
run: |
set -euo pipefail
@@ -1766,4 +1838,5 @@ jobs:
esac
- name: Run ${{ matrix.label }}
if: contains(matrix.profiles, inputs.release_test_profile)
run: ${{ matrix.command }}

View File

@@ -25,6 +25,15 @@ on:
- fresh
- upgrade
- both
release_profile:
description: Release coverage profile for live/Docker/provider breadth
required: false
default: full
type: choice
options:
- minimum
- stable
- full
rerun_group:
description: Release check group to run
required: false
@@ -61,6 +70,7 @@ jobs:
sha: ${{ steps.ref.outputs.sha }}
provider: ${{ steps.inputs.outputs.provider }}
mode: ${{ steps.inputs.outputs.mode }}
release_profile: ${{ steps.inputs.outputs.release_profile }}
rerun_group: ${{ steps.inputs.outputs.rerun_group }}
steps:
- name: Require main or release workflow ref for release checks
@@ -120,6 +130,7 @@ jobs:
RELEASE_REF_INPUT: ${{ inputs.ref }}
RELEASE_PROVIDER_INPUT: ${{ inputs.provider }}
RELEASE_MODE_INPUT: ${{ inputs.mode }}
RELEASE_PROFILE_INPUT: ${{ inputs.release_profile }}
RELEASE_RERUN_GROUP_INPUT: ${{ inputs.rerun_group }}
run: |
set -euo pipefail
@@ -127,6 +138,7 @@ jobs:
printf 'ref=%s\n' "$RELEASE_REF_INPUT"
printf 'provider=%s\n' "$RELEASE_PROVIDER_INPUT"
printf 'mode=%s\n' "$RELEASE_MODE_INPUT"
printf 'release_profile=%s\n' "$RELEASE_PROFILE_INPUT"
printf 'rerun_group=%s\n' "$RELEASE_RERUN_GROUP_INPUT"
} >> "$GITHUB_OUTPUT"
@@ -136,6 +148,7 @@ jobs:
RELEASE_SHA: ${{ steps.ref.outputs.sha }}
RELEASE_PROVIDER: ${{ inputs.provider }}
RELEASE_MODE: ${{ inputs.mode }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RELEASE_RERUN_GROUP: ${{ inputs.rerun_group }}
run: |
{
@@ -145,10 +158,75 @@ jobs:
echo "- Validated SHA: \`${RELEASE_SHA}\`"
echo "- Cross-OS provider: \`${RELEASE_PROVIDER}\`"
echo "- Cross-OS mode: \`${RELEASE_MODE}\`"
echo "- Release profile: \`${RELEASE_PROFILE}\`"
echo "- Rerun group: \`${RELEASE_RERUN_GROUP}\`"
echo "- This run will execute cross-OS release validation, install smoke, QA Lab parity, Matrix, and Telegram lanes, and the non-Parallels Docker/live/openwebui coverage from the CI migration plan."
} >> "$GITHUB_STEP_SUMMARY"
prepare_release_package:
name: Prepare release package artifact
needs: [resolve_target]
if: contains(fromJSON('["all","live-e2e","package"]'), needs.resolve_target.outputs.rerun_group)
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 60
permissions:
contents: read
outputs:
artifact_name: ${{ steps.artifact.outputs.name }}
package_sha256: ${{ steps.package.outputs.sha256 }}
package_version: ${{ steps.package.outputs.package_version }}
steps:
- name: Checkout trusted workflow ref
uses: actions/checkout@v6
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
- name: Set artifact metadata
id: artifact
run: echo "name=release-package-under-test" >> "$GITHUB_OUTPUT"
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
install-deps: "false"
- name: Resolve release package artifact
id: package
shell: bash
env:
PACKAGE_REF: ${{ needs.resolve_target.outputs.ref }}
run: |
set -euo pipefail
node scripts/resolve-openclaw-package-candidate.mjs \
--source ref \
--package-ref "$PACKAGE_REF" \
--output-dir .artifacts/docker-e2e-package \
--output-name openclaw-current.tgz \
--metadata .artifacts/docker-e2e-package/package-candidate.json \
--github-output "$GITHUB_OUTPUT"
digest="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).sha256")"
version="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).version")"
{
echo "## Release package artifact"
echo
echo "- Artifact: \`release-package-under-test\`"
echo "- Package ref: \`$PACKAGE_REF\`"
echo "- SHA-256: \`$digest\`"
echo "- Version: \`$version\`"
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload release package artifact
uses: actions/upload-artifact@v7
with:
name: release-package-under-test
path: .artifacts/docker-e2e-package/openclaw-current.tgz
retention-days: 14
if-no-files-found: error
install_smoke_release_checks:
needs: [resolve_target]
if: contains(fromJSON('["all","install-smoke"]'), needs.resolve_target.outputs.rerun_group)
@@ -177,7 +255,7 @@ jobs:
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_CHANNEL_ID }}
live_and_e2e_release_checks:
needs: [resolve_target]
needs: [resolve_target, prepare_release_package]
if: contains(fromJSON('["all","live-e2e"]'), needs.resolve_target.outputs.rerun_group)
permissions:
actions: read
@@ -189,8 +267,11 @@ jobs:
ref: ${{ needs.resolve_target.outputs.ref }}
include_repo_e2e: true
include_release_path_suites: true
include_openwebui: true
include_openwebui: ${{ needs.resolve_target.outputs.release_profile != 'minimum' }}
include_live_suites: true
release_test_profile: ${{ needs.resolve_target.outputs.release_profile }}
package_artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
package_artifact_run_id: ${{ github.run_id }}
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
@@ -239,7 +320,7 @@ jobs:
package_acceptance_release_checks:
name: Run package acceptance
needs: [resolve_target]
needs: [resolve_target, prepare_release_package]
if: contains(fromJSON('["all","package"]'), needs.resolve_target.outputs.rerun_group)
permissions:
actions: read
@@ -249,8 +330,10 @@ jobs:
uses: ./.github/workflows/package-acceptance.yml
with:
workflow_ref: ${{ github.ref_name }}
source: ref
package_ref: ${{ needs.resolve_target.outputs.ref }}
source: artifact
artifact_run_id: ${{ github.run_id }}
artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
package_sha256: ${{ needs.prepare_release_package.outputs.package_sha256 }}
suite_profile: custom
docker_lanes: bundled-channel-deps-compat plugins-offline
telegram_mode: mock-openai
@@ -608,6 +691,7 @@ jobs:
summary:
name: Verify release checks
needs:
- prepare_release_package
- install_smoke_release_checks
- cross_os_release_checks
- live_and_e2e_release_checks
@@ -627,6 +711,7 @@ jobs:
set -euo pipefail
failed=0
for item in \
"prepare_release_package=${{ needs.prepare_release_package.result }}" \
"install_smoke_release_checks=${{ needs.install_smoke_release_checks.result }}" \
"cross_os_release_checks=${{ needs.cross_os_release_checks.result }}" \
"live_and_e2e_release_checks=${{ needs.live_and_e2e_release_checks.result }}" \