name: OpenClaw Performance run-name: ${{ inputs.dispatch_id != '' && format('OpenClaw Performance {0}', inputs.dispatch_id) || 'OpenClaw Performance' }} on: schedule: - cron: "11 5 * * *" workflow_dispatch: inputs: target_ref: description: OpenClaw ref to benchmark; defaults to the workflow ref required: false default: "" type: string profile: description: Kova profile to run required: false default: diagnostic type: choice options: - smoke - diagnostic - soak - release repeat: description: Repeat count for non-profiled Kova runs required: false default: "3" type: string deep_profile: description: Run the deep-profile lane with CPU/heap/trace artifacts required: false default: false type: boolean live_openai_candidate: description: Run the live OpenAI GPT 5.6 agent-turn lane required: false default: false type: boolean fail_on_regression: description: Fail the workflow when Kova exits non-zero required: false default: false type: boolean publish_reports: description: Publish completed reports to openclaw/clawgrit-reports required: false default: true type: boolean kova_ref: description: openclaw/Kova Git ref to install required: false default: 4dde6b6022d94058abced883343de7ee8ce69917 type: string dispatch_id: description: Optional parent workflow dispatch identifier required: false default: "" type: string permissions: contents: read concurrency: group: ${{ github.event_name == 'workflow_dispatch' && format('{0}-{1}', github.workflow, github.run_id) || format('{0}-{1}', github.workflow, github.ref) }} cancel-in-progress: false env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" OCM_VERSION: v0.2.25 OCM_LINUX_X64_SHA256: 57530199d21eb5bfa29695749928b40fd2869484c7edff69b7c65bfc84f2f1aa KOVA_REPOSITORY: openclaw/Kova PERFORMANCE_MODEL_ID: gpt-5.6-luna KOVA_SCENARIO_TIMEOUT_MS: "300000" jobs: resolve_target: name: Resolve target ref runs-on: ubuntu-24.04 timeout-minutes: 10 outputs: checkout_ref: ${{ steps.resolve.outputs.checkout_ref }} tested_ref: ${{ steps.resolve.outputs.tested_ref }} tested_sha: ${{ steps.resolve.outputs.tested_sha }} steps: - name: Resolve OpenClaw target ref id: resolve env: GH_TOKEN: ${{ github.token }} TARGET_REF_INPUT: ${{ inputs.target_ref }} shell: bash run: | set -euo pipefail requested="${TARGET_REF_INPUT:-}" if [[ "$requested" == *$'\n'* || "$requested" == *$'\r'* ]]; then echo "::error::target_ref must be a single line." exit 1 fi if [[ -z "$requested" ]]; then echo "checkout_ref=${GITHUB_SHA}" >> "$GITHUB_OUTPUT" echo "tested_ref=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" echo "tested_sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT" exit 0 fi encoded_ref="$(node -e 'process.stdout.write(encodeURIComponent(process.argv[1]))' "$requested")" if ! resolved_sha="$(gh api "repos/${GITHUB_REPOSITORY}/commits/${encoded_ref}" --jq '.sha')"; then echo "::error::Unable to resolve OpenClaw target_ref '${requested}'." exit 1 fi if [[ ! "$resolved_sha" =~ ^[0-9a-f]{40}$ ]]; then echo "::error::OpenClaw target_ref '${requested}' resolved to invalid SHA '${resolved_sha}'." exit 1 fi echo "checkout_ref=$resolved_sha" >> "$GITHUB_OUTPUT" echo "tested_ref=$requested" >> "$GITHUB_OUTPUT" echo "tested_sha=$resolved_sha" >> "$GITHUB_OUTPUT" kova: name: ${{ matrix.title }} needs: resolve_target runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 240 strategy: fail-fast: false matrix: include: - lane: mock-provider title: Kova mock provider performance auth: mock repeat: input deep_profile: "false" live: "false" managed_service: "true" include_filters: "scenario:fresh-install,scenario:gateway-performance,scenario:bundled-plugin-startup,scenario:bundled-runtime-deps,scenario:agent-cold-warm-message" expected_release_entries: "fresh-install:fresh,fresh-install:onboarded-user,bundled-runtime-deps:missing-plugin-index,bundled-plugin-startup:fresh,agent-cold-warm-message:mock-openai-provider,gateway-performance:many-bundled-plugins" - lane: mock-deep-profile title: Kova mock provider deep profile auth: mock repeat: "1" deep_profile: "true" live: "false" managed_service: "true" include_filters: "scenario:fresh-install,scenario:gateway-performance,scenario:agent-cold-warm-message" expected_release_entries: "fresh-install:fresh,fresh-install:onboarded-user,agent-cold-warm-message:mock-openai-provider,gateway-performance:many-bundled-plugins" - lane: live-openai-candidate title: Kova live OpenAI GPT 5.6 agent turn auth: live repeat: "1" deep_profile: "false" live: "true" managed_service: "false" include_filters: "scenario:agent-cold-warm-message" expected_release_entries: "agent-cold-warm-message:mock-openai-provider" env: KOVA_REF: ${{ inputs.kova_ref || '4dde6b6022d94058abced883343de7ee8ce69917' }} KOVA_HOME: ${{ github.workspace }}/.artifacts/kova/home/${{ matrix.lane }} PERFORMANCE_HELPER_DIR: ${{ github.workspace }}/.artifacts/performance-workflow REPORT_DIR: ${{ github.workspace }}/.artifacts/kova/reports/${{ matrix.lane }} BUNDLE_DIR: ${{ github.workspace }}/.artifacts/kova/bundles/${{ matrix.lane }} SUMMARY_DIR: ${{ github.workspace }}/.artifacts/kova/summaries LANE_ID: ${{ matrix.lane }} TARGET_REF: ${{ needs.resolve_target.outputs.tested_ref }} EXPECTED_TESTED_SHA: ${{ needs.resolve_target.outputs.tested_sha }} PROFILE: ${{ inputs.profile || 'diagnostic' }} REQUESTED_REPEAT: ${{ inputs.repeat || '3' }} FAIL_ON_REGRESSION: ${{ inputs.fail_on_regression || 'false' }} INCLUDE_FILTERS: ${{ matrix.include_filters }} EXPECTED_RELEASE_ENTRIES: ${{ matrix.expected_release_entries }} AUTH_MODE: ${{ matrix.auth }} MATRIX_REPEAT: ${{ matrix.repeat }} MATRIX_DEEP_PROFILE: ${{ matrix.deep_profile }} MATRIX_LIVE: ${{ matrix.live }} steps: - name: Decide lane id: lane shell: bash run: | set -euo pipefail run_lane=true reason="" if [[ "$LANE_ID" == "mock-deep-profile" && "${{ github.event_name }}" != "schedule" && "${{ inputs.deep_profile || 'false' }}" != "true" ]]; then run_lane=false reason="deep_profile input is false" fi if [[ "$LANE_ID" == "live-openai-candidate" && "${{ github.event_name }}" != "schedule" && "${{ inputs.live_openai_candidate || 'false' }}" != "true" ]]; then run_lane=false reason="live_openai_candidate input is false" fi echo "run=$run_lane" >> "$GITHUB_OUTPUT" if [[ "$run_lane" != "true" ]]; then echo "Skipping ${LANE_ID}: ${reason}" >> "$GITHUB_STEP_SUMMARY" fi - name: Checkout OpenClaw if: steps.lane.outputs.run == 'true' uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.resolve_target.outputs.checkout_ref }} fetch-depth: 1 persist-credentials: false - name: Checkout performance workflow helpers if: steps.lane.outputs.run == 'true' uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.sha }} path: .artifacts/performance-workflow fetch-depth: 1 persist-credentials: false - name: Record tested revision if: steps.lane.outputs.run == 'true' shell: bash run: | set -euo pipefail tested_sha="$(git rev-parse HEAD)" if [[ "$tested_sha" != "$EXPECTED_TESTED_SHA" ]]; then echo "::error::Checked out ${tested_sha}, expected resolved target ${EXPECTED_TESTED_SHA}." exit 1 fi echo "TESTED_REF=${TARGET_REF}" >> "$GITHUB_ENV" echo "TESTED_SHA=${EXPECTED_TESTED_SHA}" >> "$GITHUB_ENV" { echo "Tested ref: ${TARGET_REF}" echo "Tested SHA: ${EXPECTED_TESTED_SHA}" echo "Workflow ref: ${GITHUB_REF_NAME}" echo "Workflow SHA: ${GITHUB_SHA}" } >> "$GITHUB_STEP_SUMMARY" - name: Set up Node environment if: steps.lane.outputs.run == 'true' uses: ./.github/actions/setup-node-env with: install-bun: "false" - name: Prepare systemd user session if: ${{ steps.lane.outputs.run == 'true' && matrix.managed_service == 'true' }} shell: bash run: | set -euo pipefail user="$(id -un)" uid="$(id -u)" test "$(ps -p 1 -o comm= | xargs)" = systemd sudo systemctl is-active --quiet systemd-logind.service sudo loginctl enable-linger "$user" sudo systemctl start "user@${uid}.service" runtime_dir="$(loginctl show-user "$user" --property=RuntimePath --value)" test -n "$runtime_dir" test -d "$runtime_dir" test -O "$runtime_dir" export XDG_RUNTIME_DIR="$runtime_dir" test -S "$XDG_RUNTIME_DIR/systemd/private" echo "XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" >> "$GITHUB_ENV" if [[ -S "$runtime_dir/bus" ]]; then export DBUS_SESSION_BUS_ADDRESS="unix:path=${runtime_dir}/bus" echo "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS" >> "$GITHUB_ENV" fi systemctl --user show-environment >/dev/null - name: Install OCM and Kova if: steps.lane.outputs.run == 'true' shell: bash run: | set -euo pipefail KOVA_SRC="${RUNNER_TEMP}/kova-src" echo "KOVA_SRC=$KOVA_SRC" >> "$GITHUB_ENV" mkdir -p "$HOME/.local/bin" "$(dirname "$KOVA_SRC")" "${RUNNER_TEMP}/ocm-install" ocm_archive="${RUNNER_TEMP}/ocm-${OCM_VERSION}-x86_64-unknown-linux-gnu.tar.gz" curl -fsSL --proto '=https' --tlsv1.2 \ --retry 8 --retry-max-time 180 --retry-all-errors --retry-connrefused \ -o "$ocm_archive" \ "https://github.com/shakkernerd/ocm/releases/download/${OCM_VERSION}/ocm-x86_64-unknown-linux-gnu.tar.gz" echo "${OCM_LINUX_X64_SHA256} ${ocm_archive}" | sha256sum -c - tar -xzf "$ocm_archive" -C "${RUNNER_TEMP}/ocm-install" install -m 0755 "${RUNNER_TEMP}/ocm-install/ocm" "$HOME/.local/bin/ocm" git init -b main "$KOVA_SRC" git -C "$KOVA_SRC" remote add origin "https://github.com/${KOVA_REPOSITORY}.git" git -C "$KOVA_SRC" fetch --filter=blob:none --depth 1 origin "$KOVA_REF" git -C "$KOVA_SRC" checkout --detach FETCH_HEAD npm --prefix "$KOVA_SRC" ci --ignore-scripts --no-audit --no-fund node - "$KOVA_SRC" <<'NODE' const fs = require("node:fs"); const path = require("node:path"); const root = process.argv[2]; const packageJsonPath = require.resolve("mock-ai-provider/package.json", { paths: [root], }); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); const bin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.["mock-ai-provider"]; if (typeof bin !== "string" || bin.length === 0) { throw new Error("mock-ai-provider does not declare its CLI entrypoint"); } fs.accessSync(path.resolve(path.dirname(packageJsonPath), bin), fs.constants.R_OK); fs.accessSync( path.join(root, "node_modules", ".bin", "mock-ai-provider"), fs.constants.X_OK, ); require.resolve("zod", { paths: [root] }); NODE cat > "$HOME/.local/bin/kova" <> "$GITHUB_PATH" - name: Configure OCM local workspace dependencies if: steps.lane.outputs.run == 'true' shell: bash run: | set -euo pipefail npm_wrapper="$PERFORMANCE_HELPER_DIR/scripts/ocm-npm-workspace-deps.mjs" workspace_dependency_dirs="" if [[ -f "${GITHUB_WORKSPACE}/packages/ai/package.json" ]]; then workspace_dependency_dirs="${GITHUB_WORKSPACE}/packages/ai" fi chmod 0755 "$npm_wrapper" { echo "OCM_INTERNAL_NPM_BIN=$npm_wrapper" echo "OPENCLAW_OCM_REAL_NPM_BIN=$(command -v npm)" echo "OPENCLAW_OCM_WORKSPACE_DEPENDENCY_DIRS=$workspace_dependency_dirs" } >> "$GITHUB_ENV" - name: Kova version and plan sanity if: steps.lane.outputs.run == 'true' shell: bash run: | set -euo pipefail repeat="$REQUESTED_REPEAT" if [[ "$MATRIX_REPEAT" != "input" ]]; then repeat="$MATRIX_REPEAT" fi plan_dir="${RUNNER_TEMP}/kova-plans" plan_json="${plan_dir}/${LANE_ID}.json" mkdir -p "$plan_dir" kova version --json kova matrix plan \ --profile "$PROFILE" \ --target "local-build:${GITHUB_WORKSPACE}" \ --include "$INCLUDE_FILTERS" \ --parallel 1 \ --repeat "$repeat" \ --json >"$plan_json" node --input-type=module - "$plan_json" <<'NODE' import fs from "node:fs"; const plan = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); const expectedFilters = process.env.INCLUDE_FILTERS.split(","); const actualFilters = plan.controls?.include; if ( !Array.isArray(actualFilters) || actualFilters.length !== expectedFilters.length || actualFilters.some((filter, index) => filter !== expectedFilters[index]) ) { throw new Error("Kova plan did not preserve the requested include filters"); } if (process.env.PROFILE === "release") { const expectedEntries = process.env.EXPECTED_RELEASE_ENTRIES.split(",").sort(); if (!Array.isArray(plan.entries)) { throw new Error("Kova release plan did not contain entries"); } const actualEntries = plan.entries .map((entry) => { if (entry.status !== "SELECTED" || !entry.scenario?.id || !entry.state?.id) { throw new Error("Kova release plan contained an invalid selected entry"); } return `${entry.scenario.id}:${entry.state.id}`; }) .sort(); if ( actualEntries.length !== expectedEntries.length || actualEntries.some((entry, index) => entry !== expectedEntries[index]) ) { throw new Error("Kova release plan entries did not match the required lane coverage"); } } NODE echo "KOVA_PLAN_JSON=$plan_json" >> "$GITHUB_ENV" echo "KOVA_LANE_REPEAT=$repeat" >> "$GITHUB_ENV" - name: Configure live OpenAI auth if: ${{ steps.lane.outputs.run == 'true' && matrix.live == 'true' }} env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} shell: bash run: | set -euo pipefail if [[ -z "${OPENAI_API_KEY:-}" ]]; then echo "OPENAI_API_KEY is not configured; live GPT 5.6 lane cannot run without live evidence." >> "$GITHUB_STEP_SUMMARY" exit 1 fi kova setup --ci --json kova setup --non-interactive --auth env-only --provider openai --env-var OPENAI_API_KEY --json - name: Run Kova id: kova if: steps.lane.outputs.run == 'true' env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} shell: bash run: | set -euo pipefail mkdir -p "$REPORT_DIR" "$BUNDLE_DIR" "$SUMMARY_DIR" repeat="$KOVA_LANE_REPEAT" args=( matrix run --profile "$PROFILE" --target "local-build:${GITHUB_WORKSPACE}" --include "$INCLUDE_FILTERS" --auth "$AUTH_MODE" --parallel 1 --repeat "$repeat" --timeout-ms "$KOVA_SCENARIO_TIMEOUT_MS" --report-dir "$REPORT_DIR" --execute --json ) if [[ "$AUTH_MODE" == "live" ]]; then args+=(--model "$PERFORMANCE_MODEL_ID") fi if [[ "$MATRIX_DEEP_PROFILE" == "true" ]]; then args+=(--deep-profile) fi if [[ "$FAIL_ON_REGRESSION" == "true" ]]; then args+=(--gate) fi log_path="$REPORT_DIR/${LANE_ID}.log" set +e kova "${args[@]}" 2>&1 | tee "$log_path" status=${PIPESTATUS[0]} set -e report_json="$(node "$PERFORMANCE_HELPER_DIR/scripts/lib/kova-report-selector.mjs" --report-dir "$REPORT_DIR")" report_md="${report_json%.json}.md" echo "status=$status" >> "$GITHUB_OUTPUT" echo "report_json=$report_json" >> "$GITHUB_OUTPUT" echo "report_md=$report_md" >> "$GITHUB_OUTPUT" set +e node "$PERFORMANCE_HELPER_DIR/scripts/lib/kova-workflow-evidence.mjs" \ --plan "$KOVA_PLAN_JSON" \ --report "$report_json" \ --profile "$PROFILE" \ --target "local-build:${GITHUB_WORKSPACE}" \ --repeat "$repeat" \ --include "$INCLUDE_FILTERS" \ --auth "$AUTH_MODE" \ --model "$PERFORMANCE_MODEL_ID" evidence_status=$? set -e effective_status="$status" if [[ "$evidence_status" == "0" && "$FAIL_ON_REGRESSION" == "true" && "$status" != "0" ]]; then if node "$PERFORMANCE_HELPER_DIR/scripts/lib/kova-report-gate.mjs" "$report_json" then effective_status=0 { echo "Kova returned a non-zero release-gate verdict, but the trusted report adapter found only filtered coverage or profiling-affected resource thresholds with no baseline regression." echo } >> "$GITHUB_STEP_SUMMARY" fi fi echo "effective_status=$effective_status" >> "$GITHUB_OUTPUT" set +e kova report bundle "$report_json" --output-dir "$BUNDLE_DIR" --json | tee "$BUNDLE_DIR/bundle.json" bundle_status=${PIPESTATUS[0]} set -e summary_path="$SUMMARY_DIR/${LANE_ID}.md" summary_args=(node "$PERFORMANCE_HELPER_DIR/scripts/kova-ci-summary.mjs" --report "$report_json" --output "$summary_path" --lane "$LANE_ID") set +e "${summary_args[@]}" summary_status=$? set -e if [[ "$summary_status" != "0" ]]; then cat > "$summary_path" <> "$summary_path" <> "$GITHUB_STEP_SUMMARY" if [[ "$evidence_status" != "0" || "$bundle_status" != "0" || "$summary_status" != "0" ]]; then exit 1 fi if [[ "$FAIL_ON_REGRESSION" == "true" && "$effective_status" != "0" ]]; then exit "$effective_status" fi - name: Validate Kova evidence if: ${{ always() && steps.lane.outputs.run == 'true' }} shell: bash run: | set -euo pipefail missing=0 if ! node "$PERFORMANCE_HELPER_DIR/scripts/lib/kova-report-selector.mjs" --report-dir "$REPORT_DIR" >/dev/null; then echo "::error::Exactly one full Kova JSON report is required for ${LANE_ID}." missing=1 fi if [[ ! -s "$BUNDLE_DIR/bundle.json" ]]; then echo "::error::Kova bundle evidence is missing for ${LANE_ID}." missing=1 fi if [[ ! -s "$SUMMARY_DIR/${LANE_ID}.md" ]]; then echo "::error::Kova summary evidence is missing for ${LANE_ID}." missing=1 fi if [[ "$missing" != "0" ]]; then exit 1 fi - name: Upload Kova artifacts if: ${{ always() && steps.lane.outputs.run == 'true' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: openclaw-performance-${{ matrix.lane }}-${{ github.run_id }}-${{ github.run_attempt }} path: | .artifacts/kova/reports/${{ matrix.lane }} .artifacts/kova/bundles/${{ matrix.lane }} .artifacts/kova/summaries/${{ matrix.lane }}.md if-no-files-found: error retention-days: ${{ matrix.deep_profile == 'true' && 14 || 30 }} source_performance: name: OpenClaw source performance probes needs: resolve_target runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 120 env: PERFORMANCE_HELPER_DIR: ${{ github.workspace }}/.artifacts/performance-workflow SOURCE_PERF_DIR: ${{ github.workspace }}/.artifacts/openclaw-performance/source/mock-provider TARGET_REF: ${{ needs.resolve_target.outputs.tested_ref }} EXPECTED_TESTED_SHA: ${{ needs.resolve_target.outputs.tested_sha }} TESTED_REF: ${{ needs.resolve_target.outputs.tested_ref }} TESTED_SHA: ${{ needs.resolve_target.outputs.tested_sha }} REQUESTED_REPEAT: ${{ inputs.repeat || '3' }} steps: - name: Checkout OpenClaw source target uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.resolve_target.outputs.checkout_ref }} fetch-depth: 1 persist-credentials: false - name: Checkout source performance helpers uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.sha }} path: .artifacts/performance-workflow fetch-depth: 1 persist-credentials: false - name: Record source performance revision shell: bash run: | set -euo pipefail tested_sha="$(git rev-parse HEAD)" if [[ "$tested_sha" != "$EXPECTED_TESTED_SHA" ]]; then echo "::error::Checked out ${tested_sha}, expected resolved target ${EXPECTED_TESTED_SHA}." exit 1 fi { echo "Tested ref: ${TESTED_REF}" echo "Tested SHA: ${TESTED_SHA}" echo "Workflow ref: ${GITHUB_REF_NAME}" echo "Workflow SHA: ${GITHUB_SHA}" } >> "$GITHUB_STEP_SUMMARY" - name: Set up source performance environment uses: ./.github/actions/setup-node-env with: install-bun: "false" - name: Fetch previous source performance baseline shell: bash run: | set -euo pipefail reports_root=".artifacts/clawgrit-baseline" mkdir -p "$reports_root" git -C "$reports_root" init -b main git -C "$reports_root" remote add origin "https://github.com/openclaw/clawgrit-reports.git" if ! git -C "$reports_root" fetch --filter=blob:none --depth=1 origin main; then echo "No previous source performance baseline could be fetched." >> "$GITHUB_STEP_SUMMARY" exit 0 fi ref_slug="$(printf '%s' "${TESTED_REF}" | tr -c 'A-Za-z0-9._-' '-')" pointer="openclaw-performance/${ref_slug}/latest-mock-provider.json" if ! git -C "$reports_root" cat-file -e "FETCH_HEAD:${pointer}"; then echo "No previous source performance baseline exists for ${TESTED_REF}." >> "$GITHUB_STEP_SUMMARY" exit 0 fi if ! latest_path="$(git -C "$reports_root" show "FETCH_HEAD:${pointer}" | node -e "const fs=require('node:fs'); const data=JSON.parse(fs.readFileSync(0,'utf8')); const value=String(data.path || ''); if (!/^openclaw-performance\\/[A-Za-z0-9._-]+\\/[0-9]+-[0-9]+\\/mock-provider$/u.test(value)) process.exit(1); process.stdout.write(value);")"; then echo "Previous source performance baseline pointer is invalid." >> "$GITHUB_STEP_SUMMARY" exit 0 fi git -C "$reports_root" sparse-checkout init --no-cone printf '/%s/source/\n' "$latest_path" | git -C "$reports_root" sparse-checkout set --stdin git -C "$reports_root" checkout --detach FETCH_HEAD baseline_source="${reports_root}/${latest_path}/source" if [[ -d "$baseline_source" ]]; then baseline_source="$(realpath "$baseline_source")" echo "SOURCE_PERF_BASELINE_DIR=$baseline_source" >> "$GITHUB_ENV" echo "Using source performance baseline: ${latest_path}/source" >> "$GITHUB_STEP_SUMMARY" else echo "Previous source performance baseline has no source directory." >> "$GITHUB_STEP_SUMMARY" fi - name: Run OpenClaw source performance probes shell: bash run: | set -euo pipefail source_runs="$REQUESTED_REPEAT" if ! [[ "$source_runs" =~ ^[0-9]+$ ]] || [[ "$source_runs" -lt 1 ]]; then source_runs=3 fi mkdir -p "$SOURCE_PERF_DIR/mock-hello" if ! node -e "const fs=require('node:fs'); const scripts=require('./package.json').scripts||{}; process.exit(scripts['test:gateway:cpu-scenarios'] && scripts['test:extensions:memory'] && scripts.openclaw && fs.existsSync('scripts/bench-cli-startup.ts') && fs.existsSync('scripts/profile-extension-memory.mjs') ? 0 : 1)"; then cat > "$SOURCE_PERF_DIR/index.md" <> "$GITHUB_STEP_SUMMARY" exit 0 fi # target_ref may predate the dedicated profile; preserve the historical full build there. if node -e "import('./scripts/build-all.mjs').then((module) => process.exit(module.BUILD_ALL_PROFILES?.sourcePerformance ? 0 : 1)).catch(() => process.exit(1))"; then OPENCLAW_BUILD_PRIVATE_QA=1 node scripts/build-all.mjs sourcePerformance else pnpm build fi pnpm test:gateway:cpu-scenarios \ --output-dir "$SOURCE_PERF_DIR/gateway-cpu" \ --runs "$source_runs" \ --warmup 1 \ --skip-qa \ --startup-case default \ --startup-case skipChannels \ --startup-case oneInternalHook \ --startup-case allInternalHooks \ --startup-case fiftyPlugins \ --startup-case fiftyStartupLazyPlugins pnpm test:extensions:memory \ -- --json "$SOURCE_PERF_DIR/extension-memory.json" for run_index in $(seq 1 "$source_runs"); do run_dir="$SOURCE_PERF_DIR/mock-hello/run-$(printf '%03d' "$run_index")" pnpm openclaw qa suite \ --provider-mode mock-openai \ --model "mock-openai/${PERFORMANCE_MODEL_ID}" \ --concurrency 1 \ --output-dir "$(realpath --relative-to="$GITHUB_WORKSPACE" "$run_dir")" \ --scenario channel-chat-baseline done gateway_home="$(mktemp -d)" gateway_port="$(node -e "const net=require('node:net'); const s=net.createServer(); s.listen(0,'127.0.0.1',()=>{ console.log(s.address().port); s.close(); });")" gateway_state="$gateway_home/.openclaw" gateway_config="$gateway_state/openclaw.json" gateway_log="$SOURCE_PERF_DIR/cli-gateway.log" gateway_pid="" mkdir -p "$gateway_state" cat > "$gateway_config" </dev/null; then kill "$gateway_pid" 2>/dev/null || true wait "$gateway_pid" 2>/dev/null || true fi rm -rf "$gateway_home" } trap cleanup_gateway EXIT OPENCLAW_HOME="$gateway_home" OPENCLAW_STATE_DIR="$gateway_state" OPENCLAW_CONFIG_PATH="$gateway_config" OPENCLAW_GATEWAY_PORT="$gateway_port" OPENCLAW_SKIP_CHANNELS=1 \ node dist/entry.js gateway run --bind loopback --port "$gateway_port" --auth none --allow-unconfigured --force \ >"$gateway_log" 2>&1 & gateway_pid="$!" for _ in $(seq 1 120); do if curl -fsS "http://127.0.0.1:${gateway_port}/healthz" >/dev/null; then break fi if ! kill -0 "$gateway_pid" 2>/dev/null; then cat "$gateway_log" >&2 exit 1 fi sleep 1 done curl -fsS "http://127.0.0.1:${gateway_port}/healthz" >/dev/null OPENCLAW_HOME="$gateway_home" OPENCLAW_STATE_DIR="$gateway_state" OPENCLAW_CONFIG_PATH="$gateway_config" OPENCLAW_GATEWAY_PORT="$gateway_port" \ node --import tsx scripts/bench-cli-startup.ts \ --case gatewayHealthJson \ --case configGetGatewayPort \ --runs "$source_runs" \ --warmup 1 \ --output "$SOURCE_PERF_DIR/cli-startup.json" cleanup_gateway trap - EXIT if node -e "const fs=require('node:fs'); const scripts=require('./package.json').scripts||{}; process.exit(scripts['test:sqlite:perf:smoke'] && fs.existsSync('scripts/bench-sqlite-state.ts') ? 0 : 1)"; then pnpm test:sqlite:perf:smoke cp .artifacts/sqlite-perf/smoke.json "$SOURCE_PERF_DIR/sqlite-perf-smoke.json" else echo "SQLite state smoke probe is not available in ${TESTED_REF}; continuing with the remaining source probes." >> "$GITHUB_STEP_SUMMARY" fi summary_args=(node "$PERFORMANCE_HELPER_DIR/scripts/openclaw-performance-source-summary.mjs" \ --source-dir "$SOURCE_PERF_DIR" \ --output "$SOURCE_PERF_DIR/index.md") if [[ -n "${SOURCE_PERF_BASELINE_DIR:-}" && -d "$SOURCE_PERF_BASELINE_DIR" ]]; then summary_args+=(--baseline-source-dir "$SOURCE_PERF_BASELINE_DIR") fi "${summary_args[@]}" cat "$SOURCE_PERF_DIR/index.md" >> "$GITHUB_STEP_SUMMARY" - name: Upload source performance artifacts if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: openclaw-performance-source-${{ github.run_id }}-${{ github.run_attempt }} path: .artifacts/openclaw-performance/source if-no-files-found: error retention-days: 30 publish: name: Publish ${{ matrix.title }} report needs: [resolve_target, kova, source_performance] if: ${{ always() && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.publish_reports == true)) && needs.resolve_target.result == 'success' && needs.kova.result != 'cancelled' && needs.source_performance.result != 'cancelled' }} runs-on: ubuntu-24.04 timeout-minutes: 30 permissions: actions: read contents: read strategy: fail-fast: false matrix: include: - lane: mock-provider title: mock provider - lane: mock-deep-profile title: mock deep profile - lane: live-openai-candidate title: live OpenAI candidate env: LANE_ID: ${{ matrix.lane }} PERFORMANCE_PUBLISHER_HELPER: ${{ github.workspace }}/.artifacts/performance-publisher/scripts/lib/kova-report-publish-files.mjs PERFORMANCE_REPORT_SELECTOR: ${{ github.workspace }}/.artifacts/performance-publisher/scripts/lib/kova-report-selector.mjs PUBLISHED_REPORT_MAX_FILE_BYTES: "50000000" REPORT_PUBLISH_REQUIRED: ${{ github.event_name == 'schedule' || inputs.profile == 'release' }} steps: - name: Decide report publication lane id: lane shell: bash run: | set -euo pipefail run_lane=true if [[ "$LANE_ID" == "mock-deep-profile" && "${{ github.event_name }}" != "schedule" && "${{ inputs.deep_profile || 'false' }}" != "true" ]]; then run_lane=false fi if [[ "$LANE_ID" == "live-openai-candidate" && "${{ github.event_name }}" != "schedule" && "${{ inputs.live_openai_candidate || 'false' }}" != "true" ]]; then run_lane=false fi echo "run=$run_lane" >> "$GITHUB_OUTPUT" - name: Checkout performance publisher helper if: steps.lane.outputs.run == 'true' uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.sha }} path: .artifacts/performance-publisher sparse-checkout: | scripts/lib/kova-report-publish-files.mjs scripts/lib/kova-report-selector.mjs sparse-checkout-cone-mode: false fetch-depth: 1 persist-credentials: false - name: Resolve Kova artifact id: artifact if: steps.lane.outputs.run == 'true' continue-on-error: ${{ env.REPORT_PUBLISH_REQUIRED != 'true' }} env: GH_TOKEN: ${{ github.token }} shell: bash run: | set -euo pipefail annotation="$([[ "$REPORT_PUBLISH_REQUIRED" == "true" ]] && printf error || printf warning)" prefix="openclaw-performance-${LANE_ID}-${GITHUB_RUN_ID}-" source_prefix="openclaw-performance-source-${GITHUB_RUN_ID}-" if ! artifacts="$(gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" --jq '.artifacts[] | select(.expired == false) | [.id, .name] | @tsv')"; then echo "::${annotation}::Unable to list Kova artifacts for this workflow run." exit 1 fi artifact_id="" producer_attempt=0 source_artifact_id="" source_producer_attempt=0 while IFS=$'\t' read -r candidate_id candidate_name; do if [[ "$candidate_name" == "$prefix"* ]]; then candidate_attempt="${candidate_name#"$prefix"}" [[ "$candidate_id" =~ ^[0-9]+$ && "$candidate_attempt" =~ ^[0-9]+$ ]] || continue if (( candidate_attempt <= GITHUB_RUN_ATTEMPT && candidate_attempt > producer_attempt )); then artifact_id="$candidate_id" producer_attempt="$candidate_attempt" fi elif [[ "$LANE_ID" == "mock-provider" && "$candidate_name" == "$source_prefix"* ]]; then candidate_attempt="${candidate_name#"$source_prefix"}" [[ "$candidate_id" =~ ^[0-9]+$ && "$candidate_attempt" =~ ^[0-9]+$ ]] || continue if (( candidate_attempt <= GITHUB_RUN_ATTEMPT && candidate_attempt > source_producer_attempt )); then source_artifact_id="$candidate_id" source_producer_attempt="$candidate_attempt" fi fi done <<< "$artifacts" if [[ -z "$artifact_id" ]]; then echo "::${annotation}::No Kova artifact is available for ${LANE_ID} through attempt ${GITHUB_RUN_ATTEMPT}." exit 1 fi artifact_ids="$artifact_id" if [[ "$LANE_ID" == "mock-provider" ]]; then if [[ -z "$source_artifact_id" ]]; then echo "::${annotation}::No source performance artifact is available through attempt ${GITHUB_RUN_ATTEMPT}." exit 1 fi artifact_ids="${artifact_ids},${source_artifact_id}" if (( source_producer_attempt > producer_attempt )); then producer_attempt="$source_producer_attempt" fi fi echo "id=$artifact_id" >> "$GITHUB_OUTPUT" echo "ids=$artifact_ids" >> "$GITHUB_OUTPUT" echo "producer_attempt=$producer_attempt" >> "$GITHUB_OUTPUT" echo "source_producer_attempt=$source_producer_attempt" >> "$GITHUB_OUTPUT" - name: Create isolated publisher paths id: paths if: steps.artifact.outputs.id != '' shell: bash run: | set -euo pipefail input_root="$(mktemp -d "${RUNNER_TEMP}/clawgrit-input.XXXXXX")" reports_root="$(mktemp -d "${RUNNER_TEMP}/clawgrit-reports.XXXXXX")" chmod 0700 "$input_root" "$reports_root" echo "input_root=$input_root" >> "$GITHUB_OUTPUT" echo "reports_root=$reports_root" >> "$GITHUB_OUTPUT" - name: Download Kova artifacts id: download if: steps.artifact.outputs.id != '' continue-on-error: ${{ env.REPORT_PUBLISH_REQUIRED != 'true' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: artifact-ids: ${{ steps.artifact.outputs.ids }} path: ${{ steps.paths.outputs.input_root }} - name: Prepare clawgrit report commit id: prepare if: steps.download.outcome == 'success' continue-on-error: ${{ env.REPORT_PUBLISH_REQUIRED != 'true' }} env: ARTIFACT_ID: ${{ steps.artifact.outputs.id }} INPUT_ROOT: ${{ steps.paths.outputs.input_root }} REPORTS_ROOT: ${{ steps.paths.outputs.reports_root }} TESTED_REF: ${{ needs.resolve_target.outputs.tested_ref }} TESTED_SHA: ${{ needs.resolve_target.outputs.tested_sha }} PRODUCER_ATTEMPT: ${{ steps.artifact.outputs.producer_attempt }} SOURCE_PRODUCER_ATTEMPT: ${{ steps.artifact.outputs.source_producer_attempt }} shell: bash run: | set -euo pipefail echo "ready=false" >> "$GITHUB_OUTPUT" annotation="$([[ "$REPORT_PUBLISH_REQUIRED" == "true" ]] && printf error || printf warning)" input_root="$(realpath "$INPUT_ROOT")" reports_root="$(realpath "$REPORTS_ROOT")" if [[ "$input_root" != "$RUNNER_TEMP/"* || "$reports_root" != "$RUNNER_TEMP/"* ]]; then echo "::${annotation}::Publisher paths escaped RUNNER_TEMP." exit 1 fi if [[ ! "$ARTIFACT_ID" =~ ^[0-9]+$ || ! "$PRODUCER_ATTEMPT" =~ ^[0-9]+$ || "$PRODUCER_ATTEMPT" -gt "$GITHUB_RUN_ATTEMPT" || ! "$SOURCE_PRODUCER_ATTEMPT" =~ ^[0-9]+$ || "$SOURCE_PRODUCER_ATTEMPT" -gt "$GITHUB_RUN_ATTEMPT" ]]; then echo "::${annotation}::Kova artifact attempt metadata is invalid." exit 1 fi # download-artifact extracts one ID directly, but nests multiple IDs # under artifact names. Recursive Kova lookup supports both layouts. mapfile -d '' report_dirs < <(find "$input_root" -type d -path "*/reports/${LANE_ID}" -print0) if [[ "${#report_dirs[@]}" != "1" ]]; then echo "::${annotation}::Expected exactly one Kova report directory for ${LANE_ID}; found ${#report_dirs[@]}." exit 1 fi if ! report_json="$(node "$PERFORMANCE_REPORT_SELECTOR" --report-dir "${report_dirs[0]}")"; then echo "::${annotation}::Exactly one full Kova JSON report is required for ${LANE_ID}." exit 1 fi report_json="$(realpath "$report_json")" if [[ "$report_json" != "$input_root/"* || ! -s "$report_json" ]]; then echo "::${annotation}::Kova JSON report is outside the downloaded artifact or empty." exit 1 fi report_md_path="${report_json%.json}.md" if [[ -e "$report_md_path" ]]; then if [[ ! -f "$report_md_path" || -L "$report_md_path" ]]; then echo "::${annotation}::Kova Markdown report is not a canonical artifact file." exit 1 fi report_md="$(realpath "$report_md_path")" if [[ "$report_md" != "$input_root/"* ]]; then echo "::${annotation}::Kova Markdown report escaped the downloaded artifact." exit 1 fi else report_md="" fi mapfile -d '' summaries < <(find "$input_root" -type f -path "*/summaries/${LANE_ID}.md" -print0) mapfile -d '' bundles < <(find "$input_root" -type d -path "*/bundles/${LANE_ID}" -print0) if [[ "${#summaries[@]}" != "1" || "${#bundles[@]}" != "1" ]]; then echo "::${annotation}::Kova summary or bundle is missing for ${LANE_ID}." exit 1 fi summary="$(realpath "${summaries[0]}")" bundle="$(realpath "${bundles[0]}")" if [[ "$summary" != "$input_root/"* || "$bundle" != "$input_root/"* || ! -s "$summary" ]]; then echo "::${annotation}::Kova summary or bundle escaped the downloaded artifact." exit 1 fi if find "$bundle" -type l -print -quit | grep -q . || find "$bundle" ! -type d ! -type f -print -quit | grep -q .; then echo "::${annotation}::Kova bundle contains a symlink or special file." exit 1 fi source_dir="" if [[ "$LANE_ID" == "mock-provider" ]]; then if [[ "$SOURCE_PRODUCER_ATTEMPT" == "0" ]]; then echo "::${annotation}::Source probe artifact attempt metadata is missing." exit 1 fi # The mock lane downloads two IDs, so its source artifact is nested # under the immutable artifact name rather than extracted directly. source_path="${input_root}/openclaw-performance-source-${GITHUB_RUN_ID}-${SOURCE_PRODUCER_ATTEMPT}/${LANE_ID}" if [[ ! -d "$source_path" ]]; then echo "::${annotation}::Source probe artifact is missing for ${LANE_ID}." exit 1 fi source_dir="$(realpath "$source_path")" if [[ "$source_dir" != "$input_root/"* ]] || find "$source_dir" -type l -print -quit | grep -q . || find "$source_dir" ! -type d ! -type f -print -quit | grep -q .; then echo "::${annotation}::Source probe artifact contains an unsafe path." exit 1 fi elif [[ "$SOURCE_PRODUCER_ATTEMPT" != "0" ]]; then echo "::${annotation}::Unexpected source probe artifact attempt for ${LANE_ID}." exit 1 fi git -C "$reports_root" init -b main git -C "$reports_root" config core.hooksPath /dev/null git -C "$reports_root" remote add origin "https://github.com/openclaw/clawgrit-reports.git" if ! timeout 120s git -C "$reports_root" fetch --depth=1 origin main; then echo "::${annotation}::Unable to fetch openclaw/clawgrit-reports before publishing." exit 1 fi git -C "$reports_root" checkout -B main FETCH_HEAD ref_slug="$(printf '%s' "$TESTED_REF" | tr -c 'A-Za-z0-9._-' '-')" if [[ -z "$ref_slug" || "$ref_slug" == "." || "$ref_slug" == ".." ]]; then echo "::${annotation}::Tested ref does not produce a safe report path." exit 1 fi run_slug="${GITHUB_RUN_ID}-${PRODUCER_ATTEMPT}" dest_rel="openclaw-performance/${ref_slug}/${run_slug}/${LANE_ID}" report_url="https://github.com/openclaw/clawgrit-reports/tree/main/${dest_rel}" if git -C "$reports_root" cat-file -e "HEAD:${dest_rel}/report.json" 2>/dev/null; then { echo "### Clawgrit report already published" echo echo "- Published report: ${report_url}" } >> "$GITHUB_STEP_SUMMARY" echo "already_published=true" >> "$GITHUB_OUTPUT" echo "report_url=$report_url" >> "$GITHUB_OUTPUT" echo "ready=true" >> "$GITHUB_OUTPUT" exit 0 fi dest="${reports_root}/${dest_rel}" mkdir -p "$dest" install -m 0644 "$report_json" "$dest/report.json" if [[ -n "$report_md" ]]; then install -m 0644 "$report_md" "$dest/report.md" fi install -m 0644 "$summary" "$dest/index.md" if [[ -n "$source_dir" ]]; then mkdir -p "$dest/source" cp -R "$source_dir"/. "$dest/source/" if [[ -f "$source_dir/index.md" ]]; then cat >> "$dest/index.md" <<'EOF' ## Source probes Additional gateway boot, memory, plugin pressure, mock hello-loop, CLI startup, and SQLite state smoke numbers are in [source/index.md](source/index.md). EOF fi fi artifact_url="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts/${ARTIFACT_ID}" { echo echo "## Full diagnostic artifact" echo echo "The complete Kova bundle remains in [Actions artifact ${ARTIFACT_ID}](${artifact_url}); its checksum is published under the bundles directory." } >> "$dest/index.md" jq -n \ --arg repository "$GITHUB_REPOSITORY" \ --arg ref "$TESTED_REF" \ --arg sha "$TESTED_SHA" \ --arg workflow_ref "$GITHUB_REF_NAME" \ --arg workflow_sha "$GITHUB_SHA" \ --arg workflow "$GITHUB_WORKFLOW" \ --arg run_id "$GITHUB_RUN_ID" \ --arg run_attempt "$PRODUCER_ATTEMPT" \ --arg lane "$LANE_ID" \ --arg path "$dest_rel" \ '{repository:$repository,ref:$ref,sha:$sha,tested_ref:$ref,tested_sha:$sha,workflow_ref:$workflow_ref,workflow_sha:$workflow_sha,workflow:$workflow,run_id:$run_id,run_attempt:$run_attempt,lane:$lane,path:$path}' \ > "${reports_root}/openclaw-performance/${ref_slug}/latest-${LANE_ID}.json" node "$PERFORMANCE_PUBLISHER_HELPER" \ --bundle-dir "$bundle" \ --bundle-destination "$dest/bundles" \ --publish-root "$dest" \ --max-file-bytes "$PUBLISHED_REPORT_MAX_FILE_BYTES" git -C "$reports_root" config user.name "openclaw-performance[bot]" git -C "$reports_root" config user.email "openclaw-performance[bot]@users.noreply.github.com" git -C "$reports_root" add -- openclaw-performance if ! git -C "$reports_root" diff --cached --quiet; then git -C "$reports_root" commit -m "perf: add OpenClaw ${LANE_ID} report ${TESTED_SHA::12}" fi report_commit="$(git -C "$reports_root" rev-parse HEAD)" echo "already_published=false" >> "$GITHUB_OUTPUT" echo "reports_root=$reports_root" >> "$GITHUB_OUTPUT" echo "report_commit=$report_commit" >> "$GITHUB_OUTPUT" echo "dest_rel=$dest_rel" >> "$GITHUB_OUTPUT" echo "report_url=$report_url" >> "$GITHUB_OUTPUT" echo "ready=true" >> "$GITHUB_OUTPUT" - name: Create clawgrit reports app token id: clawgrit_app_token if: ${{ steps.prepare.outputs.ready == 'true' && steps.prepare.outputs.already_published != 'true' }} continue-on-error: ${{ env.REPORT_PUBLISH_REQUIRED != 'true' }} uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 with: client-id: Iv23liOECG0slfuhz093 private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }} owner: openclaw repositories: clawgrit-reports permission-contents: write - name: Publish to clawgrit reports if: ${{ steps.prepare.outputs.ready == 'true' && steps.prepare.outputs.already_published != 'true' }} continue-on-error: ${{ env.REPORT_PUBLISH_REQUIRED != 'true' }} env: CLAWGRIT_REPORTS_APP_TOKEN: ${{ steps.clawgrit_app_token.outputs.token }} REPORTS_ROOT: ${{ steps.prepare.outputs.reports_root }} REPORT_COMMIT: ${{ steps.prepare.outputs.report_commit }} DEST_REL: ${{ steps.prepare.outputs.dest_rel }} REPORT_URL: ${{ steps.prepare.outputs.report_url }} shell: bash run: | set -euo pipefail annotation="$([[ "$REPORT_PUBLISH_REQUIRED" == "true" ]] && printf error || printf warning)" if [[ -z "${CLAWGRIT_REPORTS_APP_TOKEN:-}" ]]; then { echo "### Clawgrit report publish unavailable" echo echo "The repository-scoped ClawSweeper GitHub App token could not be created." } >> "$GITHUB_STEP_SUMMARY" echo "::${annotation}::ClawSweeper GitHub App token is unavailable; Kova artifacts were uploaded, but the report cannot be published." exit 1 fi token="$CLAWGRIT_REPORTS_APP_TOKEN" unset CLAWGRIT_REPORTS_APP_TOKEN auth_header="$(printf 'x-access-token:%s' "$token" | base64 | tr -d '\n')" unset token echo "::add-mask::${auth_header}" reports_root="$(realpath "$REPORTS_ROOT")" if [[ "$reports_root" != "$RUNNER_TEMP/"* || ! "$REPORT_COMMIT" =~ ^[0-9a-f]{40}$ || ! "$DEST_REL" =~ ^openclaw-performance/[A-Za-z0-9._-]+/[0-9]+-[0-9]+/[A-Za-z0-9._-]+$ ]]; then echo "::${annotation}::Prepared report metadata failed validation." exit 1 fi if [[ "$REPORT_URL" != "https://github.com/openclaw/clawgrit-reports/tree/main/${DEST_REL}" ]]; then echo "::${annotation}::Prepared report URL failed validation." exit 1 fi git_local() { git -C "$reports_root" -c core.hooksPath=/dev/null "$@" } push_report() { GIT_TERMINAL_PROMPT=0 GIT_CONFIG_COUNT=2 GIT_CONFIG_KEY_0=core.hooksPath GIT_CONFIG_VALUE_0=/dev/null GIT_CONFIG_KEY_1=http.https://github.com/.extraheader GIT_CONFIG_VALUE_1="AUTHORIZATION: basic ${auth_header}" timeout 120s git -C "$reports_root" push origin HEAD:main } write_publish_success() { { echo "### Clawgrit report published" echo echo "- Published report: ${REPORT_URL}" } >> "$GITHUB_STEP_SUMMARY" } write_publish_failure() { { echo "### Clawgrit report publish failed" echo echo "Kova artifacts were uploaded, but publishing the clawgrit report failed after $1 attempts." echo "Check the ClawSweeper GitHub App installation and contents permission for openclaw/clawgrit-reports." } >> "$GITHUB_STEP_SUMMARY" echo "::${annotation}::Kova artifacts uploaded, but clawgrit report publish failed after $1 attempts." } if [[ "$(git_local config --local --get core.hooksPath)" != "/dev/null" || "$(git_local remote get-url origin)" != "https://github.com/openclaw/clawgrit-reports.git" ]]; then echo "::${annotation}::Prepared reports checkout failed integrity validation." exit 1 fi git_local cat-file -e "${REPORT_COMMIT}^{commit}" report_commit="$REPORT_COMMIT" for attempt in 1 2 3 4 5; do if push_report; then write_publish_success exit 0 fi sleep $((attempt * 2)) if ! timeout 120s git -C "$reports_root" -c core.hooksPath=/dev/null fetch --depth=1 origin main; then if [[ "$attempt" != "5" ]]; then echo "::warning::Unable to refresh openclaw/clawgrit-reports after publish attempt ${attempt}; retrying." continue fi else if git_local cat-file -e "FETCH_HEAD:${DEST_REL}/report.json" 2>/dev/null; then # A timed-out push may have succeeded; verify its unique report before failing. write_publish_success exit 0 fi if [[ "$attempt" != "5" ]]; then git_local checkout --detach FETCH_HEAD # The run path is unique; only the shared latest pointer can conflict. # Replaying with the current commit wins that pointer deterministically. if ! git_local cherry-pick -X theirs "$report_commit"; then git_local cherry-pick --abort || true echo "::${annotation}::Unable to replay the clawgrit report after a concurrent publish." exit 1 fi report_commit="$(git_local rev-parse HEAD)" continue fi fi write_publish_failure "$attempt" exit 1 done artifact_only_guard: name: Verify artifact-only report mode needs: [resolve_target, kova, publish] if: ${{ always() && github.event_name == 'workflow_dispatch' && inputs.publish_reports != true }} runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: contents: read steps: - name: Verify report publisher stayed disabled env: PUBLISH_RESULT: ${{ needs.publish.result }} shell: bash run: | set -euo pipefail if [[ "$PUBLISH_RESULT" != "skipped" ]]; then echo "::error::Artifact-only performance mode requires the report publisher to stay skipped; got ${PUBLISH_RESULT}." exit 1 fi { echo "### Artifact-only report mode" echo echo "Clawgrit report publication stayed disabled; Kova evidence is available only as workflow artifacts." } >> "$GITHUB_STEP_SUMMARY"