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 suite_filter: description: Optional focused cross-OS suite filter, e.g. windows/packaged-upgrade or packaged-fresh required: false default: "" type: string 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 candidate_artifact_name: description: Optional current-run artifact name containing the candidate OpenClaw tarball required: false default: "" type: string candidate_artifact_run_id: description: Optional workflow run id for candidate_artifact_name required: false default: "" type: string candidate_file_name: description: Optional candidate tarball file name inside candidate_artifact_name required: false default: "" type: string candidate_version: description: Optional candidate OpenClaw package version required: false default: "" type: string candidate_source_sha: description: Optional source SHA used to build the candidate tarball required: false default: "" type: string openai_model: description: OpenAI model for release cross-OS agent-turn smoke required: false default: "" type: string advisory: description: Treat failures as advisory for the caller required: false default: false type: boolean workflow_call: inputs: advisory: description: Treat failures as advisory for the caller required: false default: false type: boolean ref: description: 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 type: string mode: description: Which release-check lanes to run required: true type: string suite_filter: description: Optional focused cross-OS suite filter, e.g. windows/packaged-upgrade or packaged-fresh required: false default: "" type: string previous_version: description: Optional baseline version for the upgrade lane (defaults to npm latest) 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 candidate_artifact_name: description: Optional current-run artifact name containing the candidate OpenClaw tarball required: false default: "" type: string candidate_artifact_run_id: description: Optional workflow run id for candidate_artifact_name required: false default: "" type: string candidate_file_name: description: Optional candidate tarball file name inside candidate_artifact_name required: false default: "" type: string candidate_version: description: Optional candidate OpenClaw package version required: false default: "" type: string candidate_source_sha: description: Optional source SHA used to build the candidate tarball required: false default: "" type: string openai_model: description: OpenAI model for release cross-OS agent-turn smoke required: false default: "" type: string secrets: OPENAI_API_KEY: required: false ANTHROPIC_API_KEY: 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 }} cancel-in-progress: ${{ inputs.ref == 'main' }} env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" NODE_VERSION: "24.15.0" OPENCLAW_REPOSITORY: openclaw/openclaw TSX_VERSION: "4.21.0" OPENCLAW_CROSS_OS_OPENAI_MODEL: ${{ inputs.openai_model || vars.OPENCLAW_CROSS_OS_OPENAI_MODEL || 'openai/gpt-5.5' }} jobs: prepare: runs-on: ubuntu-24.04 continue-on-error: ${{ inputs.advisory }} outputs: baseline_file_name: ${{ steps.baseline_metadata.outputs.file_name }} baseline_spec: ${{ steps.baseline.outputs.value }} candidate_file_name: ${{ steps.candidate_metadata.outputs.file_name }} 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: PROVIDER: ${{ inputs.provider }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} run: | set -euo pipefail case "${PROVIDER}" in openai) [[ -n "${OPENAI_API_KEY}" ]] || { echo "Missing OPENAI_API_KEY secret." >&2; exit 1; } ;; anthropic) [[ -n "${ANTHROPIC_API_KEY}" ]] || { echo "Missing ANTHROPIC_API_KEY secret." >&2; exit 1; } ;; minimax) [[ -n "${MINIMAX_API_KEY}" ]] || { echo "Missing MINIMAX_API_KEY secret." >&2; exit 1; } ;; *) echo "Unsupported provider: ${PROVIDER}" >&2 exit 1 ;; esac - 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: repository: ${{ env.OPENCLAW_REPOSITORY }} ref: ${{ steps.workflow_ref.outputs.value }} path: workflow fetch-depth: 1 persist-credentials: true - name: Checkout public source ref if: inputs.candidate_artifact_name == '' uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: repository: ${{ env.OPENCLAW_REPOSITORY }} ref: ${{ inputs.ref }} path: source fetch-depth: 0 persist-credentials: true submodules: recursive - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: ${{ env.NODE_VERSION }} - name: Setup pnpm uses: ./workflow/.github/actions/setup-pnpm-store-cache with: node-version: ${{ env.NODE_VERSION }} package-manager-file: ${{ inputs.candidate_artifact_name == '' && 'source/package.json' || 'workflow/package.json' }} lockfile-path: ${{ inputs.candidate_artifact_name == '' && 'source/pnpm-lock.yaml' || 'workflow/pnpm-lock.yaml' }} use-actions-cache: ${{ inputs.candidate_artifact_name == '' && 'true' || 'false' }} - name: Ensure pnpm store cache directory exists run: mkdir -p "$(pnpm store path --silent)" - name: Build candidate artifact once if: inputs.candidate_artifact_name == '' env: OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare run: | bash workflow/scripts/github/run-openclaw-cross-os-release-checks.sh \ --prepare-only \ --source-dir source \ --output-dir "${OUTPUT_DIR}" - name: Download current-run candidate artifact if: inputs.candidate_artifact_name != '' && inputs.candidate_artifact_run_id == '' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.candidate_artifact_name }} path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package - name: Download previous-run candidate artifact if: inputs.candidate_artifact_name != '' && inputs.candidate_artifact_run_id != '' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.candidate_artifact_name }} run-id: ${{ inputs.candidate_artifact_run_id }} github-token: ${{ github.token }} path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package - name: Capture provided candidate artifact metadata if: inputs.candidate_artifact_name != '' env: PACKAGE_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package INPUT_CANDIDATE_FILE_NAME: ${{ inputs.candidate_file_name }} INPUT_CANDIDATE_VERSION: ${{ inputs.candidate_version }} INPUT_CANDIDATE_SOURCE_SHA: ${{ inputs.candidate_source_sha }} CANDIDATE_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/candidate.json run: | node <<'NODE' const fs = require("node:fs"); const path = require("node:path"); const packageDir = process.env.PACKAGE_DIR; function resolveTarballFileName(value, label) { const fileName = typeof value === "string" ? value.trim() : ""; if ( !fileName.endsWith(".tgz") || fileName.includes("\0") || fileName !== path.basename(fileName) || fileName !== path.win32.basename(fileName) ) { throw new Error(`${label} must be a local .tgz filename.`); } return fileName; } const requestedFileName = process.env.INPUT_CANDIDATE_FILE_NAME.trim(); const files = fs.readdirSync(packageDir).filter((file) => file.endsWith(".tgz")); const selectedCandidateFileName = requestedFileName || (files.length === 1 ? files[0] : ""); if (!selectedCandidateFileName) { throw new Error(`Expected exactly one candidate .tgz in ${packageDir}; found ${files.length}.`); } const candidateFileName = resolveTarballFileName( selectedCandidateFileName, "candidate_file_name", ); if (!fs.existsSync(path.join(packageDir, candidateFileName))) { throw new Error(`Provided candidate artifact does not contain ${candidateFileName}.`); } const candidateVersion = process.env.INPUT_CANDIDATE_VERSION.trim(); if (!candidateVersion) { throw new Error("candidate_version is required when candidate_artifact_name is provided."); } const sourceSha = process.env.INPUT_CANDIDATE_SOURCE_SHA.trim(); if (!/^[0-9a-f]{40}$/iu.test(sourceSha)) { throw new Error("candidate_source_sha must be a full commit SHA when candidate_artifact_name is provided."); } fs.writeFileSync( process.env.CANDIDATE_JSON, `${JSON.stringify({ candidateFileName, candidateVersion, sourceSha }, null, 2)}\n`, ); NODE - name: Resolve baseline package spec if: ${{ inputs.mode != 'fresh' }} id: baseline env: INPUT_PREVIOUS_VERSION: ${{ inputs.previous_version }} run: | set -euo pipefail if [[ -n "${INPUT_PREVIOUS_VERSION}" ]]; then echo "value=openclaw@${INPUT_PREVIOUS_VERSION}" >> "$GITHUB_OUTPUT" exit 0 fi BASELINE_VERSION="$(npm view openclaw@latest version)" echo "value=openclaw@${BASELINE_VERSION}" >> "$GITHUB_OUTPUT" - name: Pack baseline artifact if: ${{ inputs.mode != 'fresh' }} env: BASELINE_SPEC: ${{ steps.baseline.outputs.value }} OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline run: | mkdir -p "${OUTPUT_DIR}" timeout --preserve-status 300s npm pack --ignore-scripts --json "${BASELINE_SPEC}" --pack-destination "${OUTPUT_DIR}" > "${OUTPUT_DIR}/pack.json" - name: Capture candidate metadata id: candidate_metadata env: CANDIDATE_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/candidate.json run: | node <<'NODE' >>"$GITHUB_OUTPUT" const fs = require("node:fs"); const payload = JSON.parse(fs.readFileSync(process.env.CANDIDATE_JSON, "utf8")); process.stdout.write(`file_name=${payload.candidateFileName}\n`); process.stdout.write(`version=${payload.candidateVersion}\n`); process.stdout.write(`source_sha=${payload.sourceSha}\n`); NODE - name: Capture baseline metadata if: ${{ inputs.mode != 'fresh' }} id: baseline_metadata env: BASELINE_PACK_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline/pack.json run: | node <<'NODE' >>"$GITHUB_OUTPUT" const fs = require("node:fs"); const path = require("node:path"); function resolveTarballFileName(value, label) { const fileName = typeof value === "string" ? value.trim() : ""; if ( !fileName.endsWith(".tgz") || fileName.includes("\0") || fileName !== path.basename(fileName) || fileName !== path.win32.basename(fileName) ) { throw new Error(`${label} must be a local .tgz filename.`); } return fileName; } const payload = JSON.parse(fs.readFileSync(process.env.BASELINE_PACK_JSON, "utf8")); const entry = Array.isArray(payload) ? payload.at(-1) : null; const fileName = resolveTarballFileName(entry?.filename, "Baseline npm pack filename"); process.stdout.write(`file_name=${fileName}\n`); NODE - name: Upload candidate artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }} path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package/${{ steps.candidate_metadata.outputs.file_name }} if-no-files-found: error - name: Upload baseline artifact if: ${{ inputs.mode != 'fresh' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }} path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/baseline/${{ steps.baseline_metadata.outputs.file_name }} if-no-files-found: error - name: Resolve runner matrix id: matrix env: INPUT_REF: ${{ inputs.ref }} INPUT_MODE: ${{ inputs.mode }} INPUT_SUITE_FILTER: ${{ inputs.suite_filter }} INPUT_UBUNTU_RUNNER: ${{ inputs.ubuntu_runner }} INPUT_WINDOWS_RUNNER: ${{ inputs.windows_runner }} INPUT_MACOS_RUNNER: ${{ inputs.macos_runner }} VAR_UBUNTU_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_UBUNTU_RUNNER }} VAR_WINDOWS_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_WINDOWS_RUNNER }} VAR_MACOS_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_MACOS_RUNNER }} run: | MATRIX_JSON="$(bash workflow/scripts/github/run-openclaw-cross-os-release-checks.sh \ --resolve-matrix \ --ref "${INPUT_REF}" \ --mode "${INPUT_MODE}" \ --suite-filter "${INPUT_SUITE_FILTER}" \ --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.suite_label }}" needs: prepare continue-on-error: ${{ inputs.advisory }} strategy: fail-fast: false matrix: ${{ fromJson(needs.prepare.outputs.matrix) }} runs-on: ${{ matrix.runner }} timeout-minutes: 60 steps: - name: Checkout workflow repo uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: repository: ${{ env.OPENCLAW_REPOSITORY }} ref: ${{ needs.prepare.outputs.workflow_ref }} path: workflow fetch-depth: 1 persist-credentials: true - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: ${{ env.NODE_VERSION }} - name: Setup pnpm uses: ./workflow/.github/actions/setup-pnpm-store-cache with: node-version: ${{ env.NODE_VERSION }} package-manager-file: workflow/package.json lockfile-path: workflow/pnpm-lock.yaml use-actions-cache: "false" - name: Download candidate artifact id: download_candidate continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }} path: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate - name: Retry candidate artifact download if: ${{ steps.download_candidate.outcome == 'failure' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }} path: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate - name: Download baseline artifact if: ${{ matrix.suite == 'packaged-upgrade' }} id: download_baseline continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }} path: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline - name: Retry baseline artifact download if: ${{ matrix.suite == 'packaged-upgrade' && steps.download_baseline.outcome == 'failure' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }} path: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline - name: Verify release-check inputs shell: bash env: CANDIDATE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }} BASELINE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }} OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }} SUITE: ${{ matrix.suite }} run: | mkdir -p "${OUTPUT_DIR}" if [[ ! -f "${CANDIDATE_TGZ}" ]]; then echo "::error::candidate artifact missing: ${CANDIDATE_TGZ}" exit 1 fi if [[ "${SUITE}" == "packaged-upgrade" ]] && [[ ! -f "${BASELINE_TGZ}" ]]; then echo "::error::baseline artifact missing: ${BASELINE_TGZ}" exit 1 fi - name: Run cross-OS release checks shell: bash env: 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 }} 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 }} SUITE: ${{ matrix.suite }} REF: ${{ inputs.ref }} OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }} run: | 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 bash workflow/scripts/github/run-openclaw-cross-os-release-checks.sh \ --candidate-tgz "${CANDIDATE_TGZ}" \ --candidate-version "${CANDIDATE_VERSION}" \ --source-sha "${SOURCE_SHA}" \ --baseline-spec "${BASELINE_SPEC}" \ --previous-version "${PREVIOUS_VERSION}" \ --baseline-tgz "${BASELINE_TGZ}" \ --provider "${PROVIDER}" \ --mode "${MODE}" \ --suite "${SUITE}" \ --ref "${REF}" \ "${DISCORD_ARGS[@]}" \ --output-dir "${OUTPUT_DIR}" - name: Summarize release checks if: always() shell: bash env: 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" else mkdir -p "$(dirname "${SUMMARY_PATH}")" echo "No summary generated." | tee "${SUMMARY_PATH}" >> "$GITHUB_STEP_SUMMARY" fi - name: Upload release-check artifacts if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: 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