issue implementation jobs/openclaw/inbox/issue-openclaw-openclaw-82121.md #7474
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: repair cluster worker | |
| run-name: ${{ inputs.dispatch_key && format('{0} [{1}]', contains(inputs.job, '/inbox/issue-') && format('issue implementation {0}', inputs.job) || contains(inputs.job, '/inbox/automerge-') && format('automerge repair {0}', inputs.job) || format('repair cluster {0}', inputs.job), inputs.dispatch_key) || contains(inputs.job, '/inbox/issue-') && format('issue implementation {0}', inputs.job) || contains(inputs.job, '/inbox/automerge-') && format('automerge repair {0}', inputs.job) || format('repair cluster {0}', inputs.job) }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| job: | |
| description: "Job markdown path, for example jobs/openclaw/inbox/cluster-001.md" | |
| required: true | |
| type: string | |
| dispatch_key: | |
| description: "Optional command-router idempotency key" | |
| required: false | |
| default: "" | |
| type: string | |
| job_payload: | |
| description: "Optional base64 job content from durable cluster intake" | |
| required: false | |
| default: "" | |
| type: string | |
| job_digest: | |
| description: "SHA-256 fence for job_payload" | |
| required: false | |
| default: "" | |
| type: string | |
| job_auth: | |
| description: "Materializer HMAC binding the durable job dispatch" | |
| required: false | |
| default: "" | |
| type: string | |
| automerge_session_id: | |
| description: "Stable automerge product telemetry session" | |
| required: false | |
| default: "" | |
| type: string | |
| mode: | |
| description: "Worker mode" | |
| required: true | |
| default: plan | |
| type: choice | |
| options: | |
| - plan | |
| - execute | |
| - autonomous | |
| runner: | |
| description: "Runner label for cluster planning/review work" | |
| required: true | |
| default: blacksmith-4vcpu-ubuntu-2404 | |
| type: string | |
| execution_runner: | |
| description: "Linux runner label for fix/apply execution work with delegated namespaces, recursive mount hardening, and optional Landlock defense in depth" | |
| required: true | |
| default: blacksmith-16vcpu-ubuntu-2404 | |
| type: string | |
| target_validation_timeout_ms: | |
| description: "Per-command validation budget in ms; blank uses repository config, then 480000" | |
| required: false | |
| default: "" | |
| type: string | |
| planner_sandbox: | |
| description: "Codex planning sandbox; danger-full-access is for trusted ephemeral runner fallback only" | |
| required: true | |
| default: read-only | |
| type: choice | |
| options: | |
| - read-only | |
| - danger-full-access | |
| model: | |
| description: "Internal Codex model alias" | |
| required: true | |
| default: internal | |
| type: string | |
| dry_run: | |
| description: "Render prompt and write a dry result without invoking Codex" | |
| required: true | |
| default: false | |
| type: boolean | |
| requeue: | |
| description: "Run in a dedicated lane while replacing a stale-head worker" | |
| required: false | |
| default: false | |
| type: boolean | |
| requeue_depth: | |
| description: "Bounded automatic repair retry depth" | |
| required: false | |
| default: 0 | |
| type: number | |
| permissions: | |
| contents: read | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| CLAWSWEEPER_APP_CLIENT_ID: Iv23liOECG0slfuhz093 | |
| CLAWSWEEPER_MODEL: internal | |
| CLAWSWEEPER_RUNNER: ${{ vars.CLAWSWEEPER_RUNNER || 'codex' }} | |
| CLAWSWEEPER_OPENCLAW_MODEL: ${{ secrets.CLAWSWEEPER_OPENCLAW_MODEL }} | |
| CLAWSWEEPER_OPENCLAW_PROVIDERS_JSON: ${{ secrets.CLAWSWEEPER_OPENCLAW_PROVIDERS_JSON }} | |
| CLAWSWEEPER_OPENCLAW_OPENAI_KEY: ${{ vars.CLAWSWEEPER_RUNNER == 'openclaw' && secrets.OPENAI_API_KEY || '' }} | |
| ANTHROPIC_API_KEY: ${{ vars.CLAWSWEEPER_RUNNER == 'openclaw' && secrets.ANTHROPIC_API_KEY || '' }} | |
| GEMINI_API_KEY: ${{ vars.CLAWSWEEPER_RUNNER == 'openclaw' && secrets.GEMINI_API_KEY || '' }} | |
| KIMI_API_KEY: ${{ vars.CLAWSWEEPER_RUNNER == 'openclaw' && secrets.KIMI_API_KEY || '' }} | |
| OPENROUTER_API_KEY: ${{ vars.CLAWSWEEPER_RUNNER == 'openclaw' && secrets.OPENROUTER_API_KEY || '' }} | |
| CLUSTER_JOB_PATH: ${{ inputs.job }} | |
| CLUSTER_DISPATCH_KEY: ${{ inputs.dispatch_key }} | |
| CLUSTER_JOB_PAYLOAD: ${{ inputs.job_payload }} | |
| CLUSTER_JOB_DIGEST: ${{ inputs.job_digest }} | |
| CLUSTER_JOB_AUTH: ${{ inputs.job_auth }} | |
| CLUSTER_WORKER_MODE: ${{ inputs.mode }} | |
| CLUSTER_WORKER_MODEL: ${{ inputs.model }} | |
| CLUSTER_WORKER_DRY_RUN: ${{ inputs.dry_run }} | |
| CLUSTER_WORKER_RUNNER: ${{ inputs.runner }} | |
| CLUSTER_EXECUTION_RUNNER: ${{ inputs.execution_runner }} | |
| CLUSTER_PLANNER_SANDBOX: ${{ inputs.planner_sandbox }} | |
| CLUSTER_REQUEUE_DEPTH: ${{ inputs.requeue_depth }} | |
| CLUSTER_RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| concurrency: | |
| group: ${{ inputs.requeue && format('clawsweeper-repair-requeue-{0}-{1}', inputs.job, github.run_id) || format('clawsweeper-repair-{0}', inputs.job) }} | |
| # Keep an active execute run alive so its temporary gate cleanup can finish; | |
| # GitHub still coalesces pending runs in this group to the newest dispatch. | |
| cancel-in-progress: false | |
| jobs: | |
| receipt: | |
| name: Deduplicate command dispatch receipt | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| contents: read | |
| outputs: | |
| proceed: ${{ steps.receipt.outputs.proceed }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| filter: blob:none | |
| - uses: ./.github/actions/setup-pnpm | |
| if: ${{ inputs.job_payload != '' }} | |
| with: | |
| build-script: build:repair | |
| - name: Authenticate durable intake before credentials | |
| if: ${{ inputs.job_payload != '' }} | |
| env: | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ vars.CLAWSWEEPER_ALLOWED_OWNER || 'openclaw' }} | |
| CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} | |
| run: pnpm run repair:restore-cluster-intake-job | |
| - id: receipt | |
| env: | |
| DISPATCH_KEY: ${{ inputs.dispatch_key }} | |
| JOB_PATH: ${{ inputs.job }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "$DISPATCH_KEY" ]; then | |
| echo "proceed=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| case "$JOB_PATH" in | |
| */inbox/issue-*) title="issue implementation $JOB_PATH" ;; | |
| */inbox/automerge-*) title="automerge repair $JOB_PATH" ;; | |
| *) title="repair cluster $JOB_PATH" ;; | |
| esac | |
| expected_title="${title} [${DISPATCH_KEY}]" | |
| owner="$(bash scripts/dispatch-receipt-owner.sh \ | |
| repair-cluster-worker.yml "$expected_title" "$GITHUB_RUN_ID" "Plan and review cluster")" | |
| if [ "$owner" = "owner" ]; then | |
| echo "proceed=false" >> "$GITHUB_OUTPUT" | |
| echo "An older active or successfully executed command dispatch already exists; skipping duplicate repair." | |
| exit 0 | |
| fi | |
| echo "proceed=true" >> "$GITHUB_OUTPUT" | |
| cluster: | |
| name: Plan and review cluster | |
| needs: receipt | |
| if: ${{ needs.receipt.outputs.proceed == 'true' }} | |
| runs-on: ${{ inputs.runner }} | |
| timeout-minutes: 90 | |
| outputs: | |
| allow_execute: ${{ steps.capture_gates.outputs.allow_execute }} | |
| allow_fix_pr: ${{ steps.capture_gates.outputs.allow_fix_pr }} | |
| allow_merge: ${{ steps.capture_gates.outputs.allow_merge }} | |
| job_exists: ${{ steps.check_job.outputs.job_exists }} | |
| effective_mode: ${{ steps.run_worker.outputs.effective_mode }} | |
| env: | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ vars.CLAWSWEEPER_ALLOWED_OWNER || 'openclaw' }} | |
| CLAWSWEEPER_ALLOW_EXECUTE: ${{ (inputs.mode == 'execute' || inputs.mode == 'autonomous') && vars.CLAWSWEEPER_ALLOW_EXECUTE == '1' && '1' || '0' }} | |
| CLAWSWEEPER_ALLOW_FIX_PR: ${{ (inputs.mode == 'execute' || inputs.mode == 'autonomous') && vars.CLAWSWEEPER_ALLOW_FIX_PR == '1' && '1' || '0' }} | |
| CLAWSWEEPER_ALLOW_MERGE: "0" | |
| CLAWSWEEPER_REQUESTED_ALLOW_MERGE: ${{ !contains(inputs.job, '/inbox/issue-') && (inputs.mode == 'execute' || inputs.mode == 'autonomous') && (vars.CLAWSWEEPER_ALLOW_MERGE || '0') || '0' }} | |
| CLAWSWEEPER_HYDRATE_CLUSTER_REFS: ${{ vars.CLAWSWEEPER_HYDRATE_CLUSTER_REFS || '1' }} | |
| CLAWSWEEPER_HYDRATE_COMMENTS: ${{ vars.CLAWSWEEPER_HYDRATE_COMMENTS || '1' }} | |
| CLAWSWEEPER_MAX_COMMENTS_PER_ITEM: ${{ vars.CLAWSWEEPER_MAX_COMMENTS_PER_ITEM || '30' }} | |
| CLAWSWEEPER_MAX_LINKED_REFS: ${{ vars.CLAWSWEEPER_MAX_LINKED_REFS || '20' }} | |
| CLAWSWEEPER_MAX_REVIEW_COMMENTS_PER_PR: ${{ vars.CLAWSWEEPER_MAX_REVIEW_COMMENTS_PER_PR || '50' }} | |
| CLAWSWEEPER_CODEX_TIMEOUT_MS: ${{ vars.CLAWSWEEPER_CODEX_TIMEOUT_MS || '1800000' }} | |
| CLAWSWEEPER_CODEX_PLANNER_SANDBOX: ${{ inputs.planner_sandbox }} | |
| CLAWSWEEPER_STEERABLE_CODEX: ${{ vars.CLAWSWEEPER_STEERABLE_CODEX || '0' }} | |
| CLAWSWEEPER_CRABFLEET_URL: ${{ vars.CLAWSWEEPER_CRABFLEET_URL || 'https://crabfleet.openclaw.ai' }} | |
| OPENCLAW_LOCAL_CHECK: "0" | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| filter: blob:none | |
| - name: Resolve target owner | |
| id: target | |
| env: | |
| JOB_PATH: ${{ inputs.job }} | |
| run: bash scripts/resolve-repair-job-target.sh | |
| - name: Create GitHub App token | |
| id: app_token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }} | |
| owner: ${{ steps.target.outputs.target_owner }} | |
| permission-contents: read | |
| permission-issues: read | |
| permission-pull-requests: read | |
| - name: Create target status token | |
| id: status_token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }} | |
| owner: ${{ steps.target.outputs.target_owner }} | |
| permission-issues: write | |
| - name: Create state token | |
| id: state-token | |
| uses: ./.github/actions/create-state-token | |
| with: | |
| client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }} | |
| - uses: ./.github/actions/setup-state | |
| with: | |
| coordinator-url: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} | |
| records-url: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} | |
| records-repo-slugs: ${{ steps.target.outputs.target_slug || '' }} | |
| records-item-number: ${{ steps.target.outputs.records_item_number || '' }} | |
| records-secret: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} | |
| hydrate-state-blobs: "false" | |
| token: ${{ steps.state-token.outputs.token }} | |
| fetch-depth: 1 | |
| sparse-checkout: jobs | |
| - uses: ./.github/actions/setup-pnpm | |
| with: | |
| node-version: "24.21.0" | |
| build-script: build:repair | |
| - name: Restore durable intake job | |
| if: ${{ inputs.job_payload != '' }} | |
| env: | |
| CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} | |
| run: pnpm run repair:restore-cluster-intake-job | |
| - name: Check job file | |
| id: check_job | |
| env: | |
| JOB_PATH: ${{ inputs.job }} | |
| GH_TOKEN: ${{ steps.app_token.outputs.token }} | |
| run: scripts/restore-repair-job.sh "$JOB_PATH" "this worker" | |
| - name: Capture execution gates | |
| id: capture_gates | |
| env: | |
| GH_TOKEN: ${{ steps.app_token.outputs.token }} | |
| run: bash scripts/capture-repair-execution-gates.sh | |
| - name: Resolve Codex session cache | |
| id: codex_session | |
| if: ${{ env.CLAWSWEEPER_RUNNER != 'openclaw' && steps.check_job.outputs.job_exists == '1' && env.CLAWSWEEPER_STEERABLE_CODEX == '1' }} | |
| env: | |
| JOB_PATH: ${{ inputs.job }} | |
| CODEX_AUTH_MODE: ${{ vars.CLAWSWEEPER_CODEX_AUTH_MODE || 'proxy' }} | |
| run: | | |
| set -euo pipefail | |
| key="$(printf '%s\n' "$CODEX_AUTH_MODE" "$JOB_PATH" | sha256sum | cut -d' ' -f1)" | |
| codex_home="$HOME/.clawsweeper-repair/codex-home/work-$key" | |
| echo "key=$key" >> "$GITHUB_OUTPUT" | |
| echo "codex_home=$codex_home" >> "$GITHUB_OUTPUT" | |
| echo "CLAWSWEEPER_CODEX_THREAD_STATE=$codex_home/clawsweeper-thread-state.json" >> "$GITHUB_ENV" | |
| - uses: ./.github/actions/setup-codex | |
| if: ${{ env.CLAWSWEEPER_RUNNER != 'openclaw' && steps.check_job.outputs.job_exists == '1' }} | |
| env: | |
| OPENAI_API_KEY: ${{ vars.CLAWSWEEPER_CODEX_AUTH_MODE != 'clawrouter' && secrets.OPENAI_API_KEY || '' }} | |
| CLAWSWEEPER_INTERNAL_MODEL: ${{ vars.CLAWSWEEPER_CODEX_AUTH_MODE != 'clawrouter' && secrets.CLAWSWEEPER_MODEL || '' }} | |
| CLAWSWEEPER_CLAWROUTER_CONFIG: ${{ secrets.CLAWSWEEPER_CLAWROUTER_CONFIG }} | |
| with: | |
| auth-mode: ${{ vars.CLAWSWEEPER_CODEX_AUTH_MODE || 'proxy' }} | |
| codex-home: ${{ steps.codex_session.outputs.codex_home }} | |
| - uses: ./.github/actions/setup-openclaw | |
| if: ${{ steps.check_job.outputs.job_exists == '1' }} | |
| - name: Restore Codex session | |
| id: restore_codex_session | |
| if: ${{ env.CLAWSWEEPER_RUNNER != 'openclaw' && steps.check_job.outputs.job_exists == '1' && env.CLAWSWEEPER_STEERABLE_CODEX == '1' }} | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: | | |
| ${{ steps.codex_session.outputs.codex_home }}/sessions | |
| ${{ steps.codex_session.outputs.codex_home }}/clawsweeper-thread-state.json | |
| key: ${{ runner.os }}-clawsweeper-codex-thread-${{ steps.codex_session.outputs.key }}-${{ github.run_id }}-${{ github.run_attempt }}-cluster | |
| restore-keys: | | |
| ${{ runner.os }}-clawsweeper-codex-thread-${{ steps.codex_session.outputs.key }}- | |
| - name: Register steerable Action session | |
| id: crabfleet_session | |
| if: ${{ env.CLAWSWEEPER_RUNNER != 'openclaw' && steps.check_job.outputs.job_exists == '1' && env.CLAWSWEEPER_STEERABLE_CODEX == '1' && !inputs.dry_run }} | |
| env: | |
| CLAWSWEEPER_CRABFLEET_SERVICE_TOKEN: ${{ secrets.CLAWSWEEPER_CRABFLEET_SERVICE_TOKEN }} | |
| CLAWSWEEPER_CRABFLEET_OWNER: ${{ vars.CLAWSWEEPER_CRABFLEET_OWNER }} | |
| run: pnpm run repair:action-session -- register "$CLUSTER_JOB_PATH" | |
| - name: Verify GitHub read token | |
| if: ${{ steps.check_job.outputs.job_exists == '1' }} | |
| env: | |
| GH_TOKEN: ${{ steps.app_token.outputs.token }} | |
| run: | | |
| if [ -z "${GH_TOKEN:-}" ]; then | |
| echo "GitHub App token is required" | |
| exit 1 | |
| fi | |
| gh auth status | |
| - name: Validate job | |
| if: ${{ steps.check_job.outputs.job_exists == '1' }} | |
| env: | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| run: pnpm run repair:validate-job -- "$CLUSTER_JOB_PATH" | |
| - name: Verify self-heal head | |
| id: self_heal_head | |
| if: ${{ steps.check_job.outputs.job_exists == '1' }} | |
| env: | |
| GH_TOKEN: ${{ steps.app_token.outputs.token }} | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| run: pnpm run repair:conflict-self-heal -- --verify-job-head "$CLUSTER_JOB_PATH" | |
| - name: Publish automatic implementation planning status | |
| if: ${{ steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' }} | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ steps.status_token.outputs.token }} | |
| CLAWSWEEPER_STATUS_INGEST_TOKEN: ${{ secrets.CLAWSWEEPER_STATUS_INGEST_TOKEN }} | |
| run: | | |
| pnpm run repair:issue-implementation-status -- \ | |
| --job "$CLUSTER_JOB_PATH" \ | |
| --state "Planning" \ | |
| --detail "Codex is reading the issue and repository, choosing an implementation, and planning validation." \ | |
| --run-url "$CLUSTER_RUN_URL" | |
| - name: Run worker | |
| id: run_worker | |
| if: ${{ steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' }} | |
| env: | |
| GH_TOKEN: ${{ steps.app_token.outputs.token }} | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| run: | | |
| worker_mode="$CLUSTER_WORKER_MODE" | |
| if [ "$worker_mode" != "plan" ] && [ "${CLAWSWEEPER_ALLOW_EXECUTE}" != "1" ]; then | |
| echo "CLAWSWEEPER_ALLOW_EXECUTE is not explicitly 1; rendering plan-only output for requested $worker_mode run" | |
| worker_mode="plan" | |
| fi | |
| echo "effective_mode=$worker_mode" >> "$GITHUB_OUTPUT" | |
| args=("$CLUSTER_JOB_PATH" --mode "$worker_mode" --model "$CLUSTER_WORKER_MODEL") | |
| if [ "$CLUSTER_WORKER_DRY_RUN" = "true" ]; then | |
| args+=(--dry-run) | |
| fi | |
| pnpm run repair:worker -- "${args[@]}" | |
| - name: Review worker result | |
| if: always() | |
| run: | | |
| if find .clawsweeper-repair/runs -name result.json -print -quit | grep -q .; then | |
| pnpm run repair:review-results -- .clawsweeper-repair/runs | |
| fi | |
| - name: Collect Codex debug logs | |
| if: ${{ always() && steps.check_job.outputs.job_exists == '1' }} | |
| env: | |
| CLAWSWEEPER_CODEX_DEBUG_SINCE_MINUTES: ${{ vars.CLAWSWEEPER_CODEX_DEBUG_SINCE_MINUTES || '240' }} | |
| run: | | |
| if [ -f dist/repair/collect-codex-debug.js ]; then | |
| node dist/repair/collect-codex-debug.js \ | |
| --out .clawsweeper-repair/codex-debug/cluster \ | |
| --label cluster \ | |
| --since-minutes "${CLAWSWEEPER_CODEX_DEBUG_SINCE_MINUTES}" | |
| else | |
| echo "::notice title=No Codex debug collector::dist/repair/collect-codex-debug.js is missing." | |
| fi | |
| - name: Upload Codex debug logs | |
| if: ${{ always() && steps.check_job.outputs.job_exists == '1' }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: clawsweeper-codex-debug-cluster-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: .clawsweeper-repair/codex-debug/cluster/** | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| - name: Upload worker transfer artifacts | |
| if: ${{ always() && (inputs.mode == 'execute' || inputs.mode == 'autonomous') && !inputs.dry_run }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: clawsweeper-repair-worker-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: .clawsweeper-repair/runs | |
| if-no-files-found: warn | |
| - name: Upload final worker artifacts | |
| if: ${{ always() && (!((inputs.mode == 'execute' || inputs.mode == 'autonomous') && !inputs.dry_run) || failure() || cancelled()) }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: clawsweeper-repair-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: | | |
| .clawsweeper-repair/runs/** | |
| if-no-files-found: warn | |
| - name: Save Codex session | |
| if: ${{ always() && env.CLAWSWEEPER_RUNNER != 'openclaw' && steps.check_job.outputs.job_exists == '1' && env.CLAWSWEEPER_STEERABLE_CODEX == '1' }} | |
| uses: actions/cache/save@v6 | |
| with: | |
| path: | | |
| ${{ steps.codex_session.outputs.codex_home }}/sessions | |
| ${{ steps.codex_session.outputs.codex_home }}/clawsweeper-thread-state.json | |
| key: ${{ runner.os }}-clawsweeper-codex-thread-${{ steps.codex_session.outputs.key }}-${{ github.run_id }}-${{ github.run_attempt }}-cluster | |
| - name: Record planning completion | |
| if: ${{ success() && steps.crabfleet_session.outcome == 'success' }} | |
| env: | |
| EFFECTIVE_MODE: ${{ steps.run_worker.outputs.effective_mode }} | |
| run: | | |
| if [[ "$EFFECTIVE_MODE" == "plan" || "$CLUSTER_WORKER_DRY_RUN" == "true" ]]; then | |
| pnpm run repair:action-session -- update \ | |
| --state completed \ | |
| --phase "done" \ | |
| --summary "Planning and deterministic review completed" \ | |
| --completion-reason plan_complete | |
| else | |
| pnpm run repair:action-session -- update \ | |
| --state running \ | |
| --phase planned \ | |
| --summary "Planning completed; execution runner starting" | |
| fi | |
| - name: Record planning failure | |
| if: ${{ failure() && steps.crabfleet_session.outcome == 'success' }} | |
| continue-on-error: true | |
| run: pnpm run repair:action-session -- update --state blocked --phase planning_failed --summary "Planning Action failed" --completion-reason action_failed | |
| execute: | |
| name: Execute and apply cluster actions | |
| needs: cluster | |
| if: ${{ needs.cluster.result == 'success' && needs.cluster.outputs.job_exists == '1' && needs.cluster.outputs.allow_execute == '1' && (needs.cluster.outputs.effective_mode == 'execute' || needs.cluster.outputs.effective_mode == 'autonomous') && !inputs.dry_run }} | |
| runs-on: ${{ inputs.execution_runner }} | |
| timeout-minutes: 120 | |
| permissions: | |
| actions: read | |
| contents: read | |
| env: | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ vars.CLAWSWEEPER_ALLOWED_OWNER || 'openclaw' }} | |
| CLAWSWEEPER_ALLOW_EXECUTE: ${{ needs.cluster.outputs.allow_execute == '1' && '1' || '0' }} | |
| CLAWSWEEPER_ALLOW_FIX_PR: ${{ needs.cluster.outputs.allow_fix_pr == '1' && '1' || '0' }} | |
| CLAWSWEEPER_ALLOW_MERGE: ${{ !contains(inputs.job, '/inbox/issue-') && needs.cluster.outputs.allow_merge || '0' }} | |
| CLAWSWEEPER_OPENCLAW_MODEL: ${{ contains(inputs.job, '/inbox/issue-') && format('openai/{0}', vars.CLAWSWEEPER_FIX_PR_MODEL || 'gpt-6-sol') || secrets.CLAWSWEEPER_OPENCLAW_MODEL }} | |
| CLAWSWEEPER_CODEX_REVIEW_ATTEMPTS: ${{ vars.CLAWSWEEPER_CODEX_REVIEW_ATTEMPTS || '4' }} | |
| CLAWSWEEPER_FIX_CODEX_TIMEOUT_MS: ${{ vars.CLAWSWEEPER_FIX_CODEX_TIMEOUT_MS || '1800000' }} | |
| CLAWSWEEPER_FIX_TARGET_VALIDATION_TIMEOUT_MS: ${{ inputs.target_validation_timeout_ms || vars.CLAWSWEEPER_FIX_TARGET_VALIDATION_TIMEOUT_MS || '' }} | |
| CLAWSWEEPER_FIX_STEP_TIMEOUT_MS: ${{ vars.CLAWSWEEPER_FIX_STEP_TIMEOUT_MS || '' }} | |
| CLAWSWEEPER_FIX_TIMEOUT_RESERVE_MS: ${{ vars.CLAWSWEEPER_FIX_TIMEOUT_RESERVE_MS || '1800000' }} | |
| CLAWSWEEPER_RESOLVE_REVIEW_THREADS: ${{ vars.CLAWSWEEPER_RESOLVE_REVIEW_THREADS || '1' }} | |
| CLAWSWEEPER_TARGET_VALIDATION_MODE: ${{ vars.CLAWSWEEPER_TARGET_VALIDATION_MODE || 'changed-only' }} | |
| CLAWSWEEPER_POST_FLIGHT_IGNORE_CHECKS: ${{ vars.CLAWSWEEPER_POST_FLIGHT_IGNORE_CHECKS || 'auto-response,Labeler,Stale' }} | |
| CLAWSWEEPER_MAX_ACTIVE_PRS_PER_AREA: ${{ vars.CLAWSWEEPER_MAX_ACTIVE_PRS_PER_AREA || '50' }} | |
| CLAWSWEEPER_CLOSE_SUPERSEDED_SOURCE_PRS: ${{ vars.CLAWSWEEPER_CLOSE_SUPERSEDED_SOURCE_PRS || '1' }} | |
| CLAWSWEEPER_GIT_USER_NAME: ${{ vars.CLAWSWEEPER_GIT_USER_NAME || 'clawsweeper[bot]' }} | |
| CLAWSWEEPER_GIT_USER_EMAIL: ${{ vars.CLAWSWEEPER_GIT_USER_EMAIL || '274271284+clawsweeper[bot]@users.noreply.github.com' }} | |
| CLAWSWEEPER_STEERABLE_CODEX: ${{ vars.CLAWSWEEPER_STEERABLE_CODEX || '0' }} | |
| CLAWSWEEPER_CRABFLEET_URL: ${{ vars.CLAWSWEEPER_CRABFLEET_URL || 'https://crabfleet.openclaw.ai' }} | |
| OPENCLAW_LOCAL_CHECK: "0" | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| filter: blob:none | |
| - name: Resolve target owner | |
| id: target | |
| env: | |
| JOB_PATH: ${{ inputs.job }} | |
| run: bash scripts/resolve-repair-job-target.sh | |
| - name: Create GitHub App token | |
| id: target_write_token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }} | |
| owner: ${{ steps.target.outputs.target_owner }} | |
| permission-actions: write | |
| permission-contents: write | |
| permission-issues: write | |
| permission-pull-requests: write | |
| permission-workflows: write | |
| - name: Create central requeue token | |
| id: requeue-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }} | |
| owner: openclaw | |
| repositories: clawsweeper | |
| permission-actions: write | |
| permission-contents: write | |
| - name: Create state token | |
| id: state-token | |
| uses: ./.github/actions/create-state-token | |
| with: | |
| client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }} | |
| - uses: ./.github/actions/setup-state | |
| with: | |
| coordinator-url: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} | |
| records-url: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} | |
| records-repo-slugs: ${{ steps.target.outputs.target_slug || '' }} | |
| records-item-number: ${{ steps.target.outputs.records_item_number || '' }} | |
| records-secret: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} | |
| hydrate-state-blobs: "false" | |
| token: ${{ steps.state-token.outputs.token }} | |
| fetch-depth: 1 | |
| sparse-checkout: | | |
| jobs | |
| - uses: ./.github/actions/setup-pnpm | |
| id: execute-setup-pnpm | |
| with: | |
| # OpenClaw's runtime contract rejects the stale Node 24.13 runner cache. | |
| node-version: "24.21.0" | |
| # This job publishes the action ledger with dist/clawsweeper.js, which | |
| # only the main build emits, so build both Node bundles and skip the | |
| # dashboard leg the job never runs. | |
| build-script: build:node | |
| - name: Restore durable intake job | |
| if: ${{ inputs.job_payload != '' }} | |
| env: | |
| CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} | |
| run: pnpm run repair:restore-cluster-intake-job | |
| - name: Check job file | |
| id: check_job | |
| env: | |
| JOB_PATH: ${{ inputs.job }} | |
| GH_TOKEN: ${{ steps.target_write_token.outputs.token }} | |
| run: scripts/restore-repair-job.sh "$JOB_PATH" "execute" | |
| - name: Recheck execution merge authorization | |
| id: execution_gates | |
| if: ${{ steps.check_job.outputs.job_exists == '1' }} | |
| env: | |
| GH_TOKEN: ${{ steps.target_write_token.outputs.token }} | |
| run: bash scripts/capture-repair-execution-gates.sh | |
| - name: Resolve Codex session cache | |
| id: codex_session | |
| if: ${{ env.CLAWSWEEPER_RUNNER != 'openclaw' && steps.check_job.outputs.job_exists == '1' && env.CLAWSWEEPER_STEERABLE_CODEX == '1' }} | |
| env: | |
| JOB_PATH: ${{ inputs.job }} | |
| CODEX_AUTH_MODE: ${{ vars.CLAWSWEEPER_CODEX_AUTH_MODE || 'proxy' }} | |
| run: | | |
| set -euo pipefail | |
| key="$(printf '%s\n' "$CODEX_AUTH_MODE" "$JOB_PATH" | sha256sum | cut -d' ' -f1)" | |
| codex_home="$HOME/.clawsweeper-repair/codex-home/work-$key" | |
| echo "key=$key" >> "$GITHUB_OUTPUT" | |
| echo "codex_home=$codex_home" >> "$GITHUB_OUTPUT" | |
| echo "CLAWSWEEPER_CODEX_THREAD_STATE=$codex_home/clawsweeper-thread-state.json" >> "$GITHUB_ENV" | |
| - uses: ./.github/actions/setup-codex | |
| if: ${{ env.CLAWSWEEPER_RUNNER != 'openclaw' && steps.check_job.outputs.job_exists == '1' }} | |
| env: | |
| OPENAI_API_KEY: ${{ vars.CLAWSWEEPER_CODEX_AUTH_MODE != 'clawrouter' && secrets.OPENAI_API_KEY || '' }} | |
| CLAWSWEEPER_INTERNAL_MODEL: ${{ vars.CLAWSWEEPER_CODEX_AUTH_MODE != 'clawrouter' && (contains(inputs.job, '/inbox/issue-') && (vars.CLAWSWEEPER_FIX_PR_MODEL || 'gpt-6-sol') || secrets.CLAWSWEEPER_MODEL) || '' }} | |
| CLAWSWEEPER_CLAWROUTER_CONFIG: ${{ secrets.CLAWSWEEPER_CLAWROUTER_CONFIG }} | |
| with: | |
| auth-mode: ${{ vars.CLAWSWEEPER_CODEX_AUTH_MODE || 'proxy' }} | |
| cache-suffix: target-pnpm | |
| codex-home: ${{ steps.codex_session.outputs.codex_home }} | |
| - uses: ./.github/actions/setup-openclaw | |
| if: ${{ steps.check_job.outputs.job_exists == '1' }} | |
| - name: Restore Codex session | |
| if: ${{ env.CLAWSWEEPER_RUNNER != 'openclaw' && steps.check_job.outputs.job_exists == '1' && env.CLAWSWEEPER_STEERABLE_CODEX == '1' }} | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: | | |
| ${{ steps.codex_session.outputs.codex_home }}/sessions | |
| ${{ steps.codex_session.outputs.codex_home }}/clawsweeper-thread-state.json | |
| key: ${{ runner.os }}-clawsweeper-codex-thread-${{ steps.codex_session.outputs.key }}-${{ github.run_id }}-${{ github.run_attempt }}-cluster | |
| restore-keys: | | |
| ${{ runner.os }}-clawsweeper-codex-thread-${{ steps.codex_session.outputs.key }}- | |
| - name: Resume steerable Action session | |
| id: crabfleet_session | |
| if: ${{ env.CLAWSWEEPER_RUNNER != 'openclaw' && steps.check_job.outputs.job_exists == '1' && env.CLAWSWEEPER_STEERABLE_CODEX == '1' }} | |
| env: | |
| CLAWSWEEPER_CRABFLEET_SERVICE_TOKEN: ${{ secrets.CLAWSWEEPER_CRABFLEET_SERVICE_TOKEN }} | |
| CLAWSWEEPER_CRABFLEET_OWNER: ${{ vars.CLAWSWEEPER_CRABFLEET_OWNER }} | |
| run: pnpm run repair:action-session -- register "$CLUSTER_JOB_PATH" | |
| - name: Download worker artifacts | |
| if: ${{ steps.check_job.outputs.job_exists == '1' }} | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: clawsweeper-repair-worker-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: .clawsweeper-repair/runs | |
| - name: Validate job | |
| if: ${{ steps.check_job.outputs.job_exists == '1' }} | |
| env: | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| run: pnpm run repair:validate-job -- "$CLUSTER_JOB_PATH" | |
| - name: Verify self-heal head | |
| id: self_heal_head | |
| if: ${{ steps.check_job.outputs.job_exists == '1' }} | |
| env: | |
| GH_TOKEN: ${{ steps.target_write_token.outputs.token }} | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| run: pnpm run repair:conflict-self-heal -- --verify-job-head "$CLUSTER_JOB_PATH" | |
| - name: Verify Linux validation containment | |
| if: ${{ steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' && env.CLAWSWEEPER_ALLOW_FIX_PR == '1' }} | |
| run: pnpm run repair:containment-smoke | |
| - name: Setup pinned Bun for target validation | |
| if: ${{ steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' && env.CLAWSWEEPER_ALLOW_FIX_PR == '1' }} | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 | |
| with: | |
| bun-version: 1.4.2 | |
| - name: Publish automatic implementation build status | |
| if: ${{ steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' && env.CLAWSWEEPER_ALLOW_FIX_PR == '1' }} | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ steps.target_write_token.outputs.token }} | |
| CLAWSWEEPER_STATUS_INGEST_TOKEN: ${{ secrets.CLAWSWEEPER_STATUS_INGEST_TOKEN }} | |
| run: | | |
| pnpm run repair:issue-implementation-status -- \ | |
| --job "$CLUSTER_JOB_PATH" \ | |
| --state "Building" \ | |
| --detail "Codex is editing, validating, and reviewing the implementation before a branch or pull request is published." \ | |
| --run-url "$CLUSTER_RUN_URL" | |
| - name: Resolve repair timeout budget | |
| id: repair_budget | |
| if: ${{ steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' && env.CLAWSWEEPER_ALLOW_FIX_PR == '1' }} | |
| run: node scripts/resolve-repair-timeout-budget.mjs "$CLUSTER_JOB_PATH" | |
| - name: Execute credited fix artifact | |
| id: execute_fix | |
| if: ${{ steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' && env.CLAWSWEEPER_ALLOW_FIX_PR == '1' }} | |
| timeout-minutes: ${{ fromJSON(steps.repair_budget.outputs.timeout_minutes) }} | |
| env: | |
| GH_TOKEN: ${{ steps.target_write_token.outputs.token }} | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| CLAWSWEEPER_AUTOMERGE_SESSION_ID: ${{ inputs.automerge_session_id }} | |
| # OpenClaw agents intentionally use the CI-only Test Server lease capability directly. | |
| # Keep free-form repository skill access; do not replace it with a fixed proof runner. | |
| OPENCLAW_QA_CONVEX_SITE_URL: ${{ steps.target.outputs.target_slug == 'openclaw-openclaw' && secrets.OPENCLAW_QA_CONVEX_SITE_URL || '' }} | |
| OPENCLAW_QA_CONVEX_SECRET_CI: ${{ steps.target.outputs.target_slug == 'openclaw-openclaw' && secrets.OPENCLAW_QA_CONVEX_SECRET_CI || '' }} | |
| run: pnpm run repair:execute-fix -- "$CLUSTER_JOB_PATH" --latest --defer-publication | |
| - name: Renew target write token for post-flight | |
| id: target_post_flight_token | |
| if: ${{ always() && steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' }} | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }} | |
| private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }} | |
| owner: ${{ steps.target.outputs.target_owner }} | |
| permission-actions: write | |
| permission-contents: write | |
| permission-issues: write | |
| permission-pull-requests: write | |
| permission-workflows: write | |
| - name: Publish deferred fix outcome | |
| if: ${{ always() && steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' }} | |
| env: | |
| GH_TOKEN: ${{ steps.target_post_flight_token.outputs.token }} | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| CLAWSWEEPER_AUTOMERGE_SESSION_ID: ${{ inputs.automerge_session_id }} | |
| run: pnpm run repair:execute-fix -- "$CLUSTER_JOB_PATH" --latest --publish-report-only | |
| - name: Record deterministic validation phase | |
| if: ${{ success() && steps.crabfleet_session.outcome == 'success' }} | |
| run: pnpm run repair:action-session -- update --state running --phase post_flight --summary "Codex changes validated; applying GitHub post-flight gates" | |
| - name: Apply safe closure actions | |
| if: ${{ steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' }} | |
| env: | |
| GH_TOKEN: ${{ steps.target_post_flight_token.outputs.token }} | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| run: pnpm run repair:apply-result -- "$CLUSTER_JOB_PATH" --latest | |
| - name: Post-flight finalize fix PRs | |
| id: post_flight | |
| if: ${{ steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' && env.CLAWSWEEPER_ALLOW_FIX_PR == '1' }} | |
| env: | |
| GH_TOKEN: ${{ steps.target_post_flight_token.outputs.token }} | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| run: pnpm run repair:post-flight -- "$CLUSTER_JOB_PATH" --latest | |
| - name: Apply post-flight closeouts | |
| if: ${{ steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' }} | |
| env: | |
| GH_TOKEN: ${{ steps.target_post_flight_token.outputs.token }} | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| run: pnpm run repair:apply-result -- "$CLUSTER_JOB_PATH" --latest | |
| - name: Tag ClawSweeper targets | |
| if: ${{ always() && steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' }} | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ steps.target_post_flight_token.outputs.token }} | |
| CLAWSWEEPER_ALLOWED_OWNER: ${{ steps.target.outputs.target_owner }} | |
| run: pnpm run repair:tag-clawsweeper -- .clawsweeper-repair/runs --apply --live --open-branches false --report .clawsweeper-repair/runs/clawsweeper-label-report.json | |
| - name: Publish automatic implementation completion status | |
| if: ${{ always() && steps.check_job.outputs.job_exists == '1' }} | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ steps.target_post_flight_token.outputs.token }} | |
| CLAWSWEEPER_STATUS_INGEST_TOKEN: ${{ secrets.CLAWSWEEPER_STATUS_INGEST_TOKEN }} | |
| EXECUTE_OUTCOME: ${{ steps.execute_fix.outcome }} | |
| POST_FLIGHT_OUTCOME: ${{ steps.post_flight.outcome }} | |
| run: | | |
| set -euo pipefail | |
| state="Blocked" | |
| detail="The automatic implementation worker stopped before all deterministic gates completed. Open the workflow run for the exact blocker." | |
| pr_url="" | |
| report="" | |
| post_flight_report="" | |
| if [ "$EXECUTE_OUTCOME" = "success" ]; then | |
| report="$( | |
| find .clawsweeper-repair/runs -name fix-execution-report.json -type f -print -quit | |
| )" | |
| if [ -n "$report" ]; then | |
| pr_url="$( | |
| jq -r ' | |
| [ | |
| .actions[]? | |
| | select( | |
| .action == "open_fix_pr" and | |
| (.status | IN("opened", "executed", "updated")) and | |
| ((.pr_url // "") != "") | |
| ) | |
| | .pr_url | |
| ][-1] // empty | |
| ' "$report" | |
| )" | |
| fi | |
| fi | |
| if [ "$EXECUTE_OUTCOME" = "success" ] && [ "$POST_FLIGHT_OUTCOME" = "success" ] && [ -n "$report" ]; then | |
| post_flight_report="${report%/*}/post-flight-report.json" | |
| if [ ! -f "$post_flight_report" ]; then | |
| post_flight_report="" | |
| fi | |
| if [ -n "$pr_url" ]; then | |
| state="PR Opened" | |
| detail="The implementation PR is open. Normal pull request checks and maintainer review continue on $pr_url." | |
| post_flight_ready="false" | |
| if [ -n "$post_flight_report" ]; then | |
| post_flight_ready="$( | |
| jq -r --arg pr_url "$pr_url" ' | |
| any( | |
| .actions[]?; | |
| .action == "finalize_fix_pr" and | |
| .source_action == "open_fix_pr" and | |
| .status == "ready" and | |
| ((.target // "") == $pr_url) | |
| ) | |
| ' "$post_flight_report" | |
| )" | |
| fi | |
| if [ "$post_flight_ready" = "true" ]; then | |
| detail="The implementation PR passed the worker's edit, validation, review, publish, and post-flight gates." | |
| else | |
| reason="$( | |
| jq -r ' | |
| [ | |
| .actions[]? | |
| | select(.status | IN("blocked", "failed", "skipped")) | |
| | .reason | |
| | select(type == "string" and length > 0) | |
| ][-1] // empty | |
| ' "$post_flight_report" 2>/dev/null || true | |
| )" | |
| if [ -z "$reason" ]; then | |
| reason="$( | |
| jq -r ' | |
| [ | |
| .actions[]? | |
| | select(.status | IN("blocked", "failed", "skipped")) | |
| | .reason | |
| | select(type == "string" and length > 0) | |
| ][-1] // .reason // empty | |
| ' "$report" | |
| )" | |
| fi | |
| if [ -n "$reason" ]; then | |
| detail="The implementation PR is open. Post-flight status: $reason" | |
| fi | |
| fi | |
| fi | |
| fi | |
| pnpm run repair:issue-implementation-status -- \ | |
| --job "$CLUSTER_JOB_PATH" \ | |
| --state "$state" \ | |
| --detail "$detail" \ | |
| --run-url "$CLUSTER_RUN_URL" \ | |
| --pr-url "$pr_url" | |
| - name: Detect repair requeue requests | |
| id: repair_requeue | |
| if: ${{ always() && steps.check_job.outputs.job_exists == '1' && steps.self_heal_head.outputs.matched != 'false' && env.CLAWSWEEPER_ALLOW_EXECUTE == '1' && env.CLAWSWEEPER_ALLOW_FIX_PR == '1' }} | |
| run: | | |
| set -euo pipefail | |
| count="$(pnpm run --silent workflow -- count-requeue-required --dir .clawsweeper-repair/runs)" | |
| echo "count=$count" >> "$GITHUB_OUTPUT" | |
| - uses: ./.github/actions/setup-action-ledger | |
| id: repair-requeue-ledger | |
| if: ${{ always() && steps.repair_requeue.outputs.count != '' && steps.repair_requeue.outputs.count != '0' }} | |
| - name: Requeue source-head repair races | |
| id: requeue_dispatch | |
| if: ${{ always() && steps.repair-requeue-ledger.outcome == 'success' && steps.repair_requeue.outputs.count != '' && steps.repair_requeue.outputs.count != '0' }} | |
| env: | |
| GH_TOKEN: ${{ steps.requeue-token.outputs.token }} | |
| CLAWSWEEPER_MUTATION_TOKEN_SOURCE: clawsweeper-app | |
| REQUEUE_COUNT: ${{ steps.repair_requeue.outputs.count }} | |
| run: | | |
| set -euo pipefail | |
| echo "Requeueing $REQUEUE_COUNT source-head race repair result(s) against the latest head." | |
| pnpm run repair:requeue -- "$CLUSTER_JOB_PATH" \ | |
| --mode "$CLUSTER_WORKER_MODE" \ | |
| --execute \ | |
| --open-execute-window \ | |
| --wait-for-capacity \ | |
| --source-job-path "$CLUSTER_JOB_PATH" \ | |
| --requeue-depth "$CLUSTER_REQUEUE_DEPTH" \ | |
| --max-requeue-depth 1 \ | |
| --runner "$CLUSTER_WORKER_RUNNER" \ | |
| --execution-runner "$CLUSTER_EXECUTION_RUNNER" \ | |
| --model "$CLUSTER_WORKER_MODEL" | |
| - name: Finalize repair requeue action ledger | |
| if: ${{ always() && steps.execute-setup-pnpm.outcome == 'success' && steps.repair-requeue-ledger.outcome == 'success' && steps.repair_requeue.outputs.count != '' && steps.repair_requeue.outputs.count != '0' }} | |
| run: | | |
| set -euo pipefail | |
| : "${CLAWSWEEPER_ACTION_LEDGER_OUTPUT_ROOT:?setup-action-ledger output root is required}" | |
| mkdir -p .artifacts | |
| pnpm run --silent repair:action-ledger -- finalize \ | |
| --lane repair-requeue \ | |
| > .artifacts/repair-requeue-action-ledger-manifest.json | |
| - name: Publish immutable repair requeue action ledger | |
| if: ${{ always() && steps.execute-setup-pnpm.outcome == 'success' && steps.repair-requeue-ledger.outcome == 'success' && steps.repair_requeue.outputs.count != '' && steps.repair_requeue.outputs.count != '0' }} | |
| env: | |
| CLAWSWEEPER_WEBHOOK_SECRET: ${{ secrets.CLAWSWEEPER_WEBHOOK_SECRET }} | |
| QUEUE_URL: ${{ vars.CLAWSWEEPER_EXACT_REVIEW_QUEUE_URL || 'https://clawsweeper.openclaw.ai' }} | |
| run: | | |
| set -euo pipefail | |
| source_root="${CLAWSWEEPER_ACTION_LEDGER_OUTPUT_ROOT:?setup-action-ledger output root is required}" | |
| manifest_file=".artifacts/repair-requeue-action-ledger-manifest.json" | |
| test -s "$manifest_file" | |
| event_paths_file=".artifacts/repair-requeue-action-ledger-paths.txt" | |
| import_result_file=".artifacts/repair-requeue-action-ledger-import.json" | |
| pnpm run --silent repair:action-ledger -- publish \ | |
| --lane repair-requeue \ | |
| --manifest "$manifest_file" \ | |
| --source-root "$source_root" \ | |
| --state-root . > "$import_result_file" | |
| if ! jq -e --slurpfile manifest "$manifest_file" \ | |
| '.eventPaths == $manifest[0].event_paths' \ | |
| "$import_result_file" >/dev/null; then | |
| echo "Imported repair requeue action event paths do not match the finalized manifest." >&2 | |
| exit 1 | |
| fi | |
| jq -r '.paths[]?' "$import_result_file" | | |
| sort -u > "$event_paths_file" | |
| if [ ! -s "$event_paths_file" ]; then | |
| echo "Repair requeue action event shards existed but no paths were imported." >&2 | |
| exit 1 | |
| fi | |
| node dist/clawsweeper.js publish-action-event-paths \ | |
| --paths-file "$event_paths_file" | |
| - name: Record requeued work | |
| if: ${{ always() && steps.crabfleet_session.outcome == 'success' && steps.repair_requeue.outputs.count != '' && steps.repair_requeue.outputs.count != '0' && steps.requeue_dispatch.outcome == 'success' }} | |
| run: pnpm run repair:action-session -- update --state running --phase requeued --summary "Source head changed; replacement Action dispatched" | |
| - name: Record work completion | |
| if: ${{ success() && steps.crabfleet_session.outcome == 'success' && (steps.repair_requeue.outputs.count == '' || steps.repair_requeue.outputs.count == '0') }} | |
| run: pnpm run repair:action-session -- update --state completed --phase "done" --summary "Repair, validation, review, push, and post-flight gates completed" --completion-reason gates_passed | |
| - name: Collect Codex debug logs | |
| if: ${{ always() && steps.check_job.outputs.job_exists == '1' }} | |
| env: | |
| CLAWSWEEPER_CODEX_DEBUG_SINCE_MINUTES: ${{ vars.CLAWSWEEPER_CODEX_DEBUG_SINCE_MINUTES || '240' }} | |
| run: | | |
| if [ -f dist/repair/collect-codex-debug.js ]; then | |
| node dist/repair/collect-codex-debug.js \ | |
| --out .clawsweeper-repair/codex-debug/execute \ | |
| --label execute \ | |
| --since-minutes "${CLAWSWEEPER_CODEX_DEBUG_SINCE_MINUTES}" | |
| else | |
| echo "::notice title=No Codex debug collector::dist/repair/collect-codex-debug.js is missing." | |
| fi | |
| - name: Upload Codex debug logs | |
| if: ${{ always() && steps.check_job.outputs.job_exists == '1' }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: clawsweeper-codex-debug-execute-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: .clawsweeper-repair/codex-debug/execute/** | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| - name: Upload final worker artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: clawsweeper-repair-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: | | |
| .clawsweeper-repair/runs/** | |
| if-no-files-found: warn | |
| - name: Save Codex session | |
| if: ${{ always() && env.CLAWSWEEPER_RUNNER != 'openclaw' && steps.check_job.outputs.job_exists == '1' && env.CLAWSWEEPER_STEERABLE_CODEX == '1' }} | |
| uses: actions/cache/save@v6 | |
| with: | |
| path: | | |
| ${{ steps.codex_session.outputs.codex_home }}/sessions | |
| ${{ steps.codex_session.outputs.codex_home }}/clawsweeper-thread-state.json | |
| key: ${{ runner.os }}-clawsweeper-codex-thread-${{ steps.codex_session.outputs.key }}-${{ github.run_id }}-${{ github.run_attempt }}-execute | |
| - name: Record work failure | |
| if: ${{ always() && failure() && steps.crabfleet_session.outcome == 'success' && (steps.repair_requeue.outputs.count == '' || steps.repair_requeue.outputs.count == '0' || steps.requeue_dispatch.outcome != 'success') }} | |
| continue-on-error: true | |
| run: pnpm run repair:action-session -- update --state blocked --phase action_failed --summary "Repair Action failed before all completion gates passed" --completion-reason action_failed | |
| - name: Reconcile failed automerge telemetry | |
| if: ${{ always() && failure() && inputs.automerge_session_id != '' && (steps.repair_requeue.outputs.count == '' || steps.repair_requeue.outputs.count == '0' || steps.requeue_dispatch.outcome != 'success') }} | |
| continue-on-error: true | |
| env: | |
| AUTOMERGE_SESSION_ID: ${{ inputs.automerge_session_id }} | |
| AUTOMERGE_RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| CLAWSWEEPER_STATUS_INGEST_TOKEN: ${{ secrets.CLAWSWEEPER_STATUS_INGEST_TOKEN }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| node scripts/dashboard-reconcile-automerge.ts \ | |
| --session-id "$AUTOMERGE_SESSION_ID" \ | |
| --run-url "$AUTOMERGE_RUN_URL" \ | |
| --run-conclusion failure |