Skip to content

issue implementation jobs/openclaw/inbox/issue-openclaw-openclaw-82121.md #7474

issue implementation jobs/openclaw/inbox/issue-openclaw-openclaw-82121.md

issue implementation jobs/openclaw/inbox/issue-openclaw-openclaw-82121.md #7474

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