327 lines
14 KiB
YAML
327 lines
14 KiB
YAML
name: cluster worker
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
job:
|
|
description: "Job markdown path, for example jobs/openclaw/inbox/cluster-001.md"
|
|
required: true
|
|
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: "Runner label for fix/apply execution work"
|
|
required: true
|
|
default: blacksmith-16vcpu-ubuntu-2404
|
|
type: string
|
|
model:
|
|
description: "Codex model"
|
|
required: true
|
|
default: gpt-5.5
|
|
type: string
|
|
dry_run:
|
|
description: "Render prompt and write a dry result without invoking Codex"
|
|
required: true
|
|
default: false
|
|
type: boolean
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
|
|
concurrency:
|
|
group: projectclownfish-${{ inputs.job }}-${{ inputs.mode }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
cluster:
|
|
name: Plan and review cluster
|
|
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 }}
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
CODEX_API_KEY: ${{ secrets.CODEX_API_KEY || secrets.OPENAI_API_KEY }}
|
|
CLOWNFISH_ALLOWED_OWNER: ${{ vars.CLOWNFISH_ALLOWED_OWNER || 'openclaw' }}
|
|
CLOWNFISH_ALLOW_EXECUTE: ${{ (inputs.mode == 'execute' || inputs.mode == 'autonomous') && vars.CLOWNFISH_ALLOW_EXECUTE == '1' && '1' || '0' }}
|
|
CLOWNFISH_ALLOW_FIX_PR: ${{ (inputs.mode == 'execute' || inputs.mode == 'autonomous') && vars.CLOWNFISH_ALLOW_FIX_PR == '1' && '1' || '0' }}
|
|
CLOWNFISH_ALLOW_MERGE: ${{ (inputs.mode == 'execute' || inputs.mode == 'autonomous') && (vars.CLOWNFISH_ALLOW_MERGE || '0') || '0' }}
|
|
CLOWNFISH_HYDRATE_CLUSTER_REFS: ${{ vars.CLOWNFISH_HYDRATE_CLUSTER_REFS || '1' }}
|
|
CLOWNFISH_HYDRATE_COMMENTS: ${{ vars.CLOWNFISH_HYDRATE_COMMENTS || '1' }}
|
|
CLOWNFISH_MAX_COMMENTS_PER_ITEM: ${{ vars.CLOWNFISH_MAX_COMMENTS_PER_ITEM || '30' }}
|
|
CLOWNFISH_MAX_LINKED_REFS: ${{ vars.CLOWNFISH_MAX_LINKED_REFS || '20' }}
|
|
CLOWNFISH_MAX_REVIEW_COMMENTS_PER_PR: ${{ vars.CLOWNFISH_MAX_REVIEW_COMMENTS_PER_PR || '50' }}
|
|
CLOWNFISH_CODEX_TIMEOUT_MS: ${{ vars.CLOWNFISH_CODEX_TIMEOUT_MS || '1800000' }}
|
|
CLOWNFISH_CODEX_REASONING_EFFORT: ${{ vars.CLOWNFISH_CODEX_REASONING_EFFORT || 'medium' }}
|
|
CLOWNFISH_MODEL: ${{ inputs.model || vars.CLOWNFISH_MODEL || 'gpt-5.5' }}
|
|
CODEX_CLI_VERSION: ${{ vars.CLOWNFISH_CODEX_CLI_VERSION || '0.125.0' }}
|
|
OPENCLAW_LOCAL_CHECK: "0"
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
|
|
- name: Create GitHub App token
|
|
id: app_token
|
|
continue-on-error: true
|
|
uses: actions/create-github-app-token@v3
|
|
with:
|
|
app-id: ${{ vars.CLOWNFISH_APP_ID }}
|
|
private-key: ${{ secrets.CLOWNFISH_APP_PRIVATE_KEY }}
|
|
owner: ${{ vars.CLOWNFISH_ALLOWED_OWNER || github.repository_owner }}
|
|
permission-contents: read
|
|
permission-issues: read
|
|
permission-pull-requests: read
|
|
|
|
- name: Capture execution gates
|
|
id: capture_gates
|
|
run: |
|
|
{
|
|
echo "allow_execute=${CLOWNFISH_ALLOW_EXECUTE}"
|
|
echo "allow_fix_pr=${CLOWNFISH_ALLOW_FIX_PR}"
|
|
echo "allow_merge=${CLOWNFISH_ALLOW_MERGE}"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- uses: actions/setup-node@v5
|
|
with:
|
|
node-version: "24"
|
|
|
|
- name: Cache Codex CLI and npm downloads
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/.npm
|
|
~/.projectclownfish/codex
|
|
key: ${{ runner.os }}-node24-codex-${{ env.CODEX_CLI_VERSION }}-v1
|
|
restore-keys: |
|
|
${{ runner.os }}-node24-codex-
|
|
|
|
- name: Verify GitHub read token
|
|
env:
|
|
GH_TOKEN: ${{ steps.app_token.outputs.token || secrets.CLOWNFISH_READ_GH_TOKEN || github.token }}
|
|
run: |
|
|
if [ -z "${GH_TOKEN:-}" ]; then
|
|
echo "CLOWNFISH_READ_GH_TOKEN is required"
|
|
exit 1
|
|
fi
|
|
gh auth status
|
|
|
|
- name: Install Codex CLI
|
|
run: |
|
|
set -euo pipefail
|
|
npm config set prefix "$HOME/.projectclownfish/codex"
|
|
npm config set cache "$HOME/.npm"
|
|
echo "$HOME/.projectclownfish/codex/bin" >> "$GITHUB_PATH"
|
|
export PATH="$HOME/.projectclownfish/codex/bin:$PATH"
|
|
|
|
if ! command -v codex >/dev/null 2>&1 || ! codex --version | grep -Fq "$CODEX_CLI_VERSION"; then
|
|
npm install -g "@openai/codex@$CODEX_CLI_VERSION" --prefer-offline --no-audit --no-fund
|
|
fi
|
|
codex --version
|
|
|
|
- name: Authenticate Codex
|
|
run: |
|
|
test -n "${OPENAI_API_KEY:-}"
|
|
test -n "${CODEX_API_KEY:-}"
|
|
printenv OPENAI_API_KEY | codex login --with-api-key >/dev/null
|
|
|
|
- name: Validate job
|
|
run: npm run validate:job -- "${{ inputs.job }}"
|
|
|
|
- name: Run worker
|
|
env:
|
|
GH_TOKEN: ${{ steps.app_token.outputs.token || secrets.CLOWNFISH_READ_GH_TOKEN || github.token }}
|
|
run: |
|
|
worker_mode="${{ inputs.mode }}"
|
|
if [ "$worker_mode" != "plan" ] && [ "${CLOWNFISH_ALLOW_EXECUTE}" != "1" ]; then
|
|
echo "CLOWNFISH_ALLOW_EXECUTE is not explicitly 1; rendering plan-only output for requested $worker_mode run"
|
|
worker_mode="plan"
|
|
fi
|
|
args=("${{ inputs.job }}" --mode "$worker_mode")
|
|
if [ "${{ inputs.dry_run }}" = "true" ]; then
|
|
args+=(--dry-run)
|
|
fi
|
|
npm run worker -- "${args[@]}"
|
|
|
|
- name: Review worker result
|
|
if: always()
|
|
run: |
|
|
if find .projectclownfish/runs -name result.json -print -quit | grep -q .; then
|
|
npm run review-results -- .projectclownfish/runs
|
|
fi
|
|
|
|
- name: Upload worker transfer artifacts
|
|
if: ${{ always() && (inputs.mode == 'execute' || inputs.mode == 'autonomous') && !inputs.dry_run }}
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: projectclownfish-worker-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: .projectclownfish/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: projectclownfish-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: |
|
|
.projectclownfish/runs/**
|
|
if-no-files-found: warn
|
|
|
|
execute:
|
|
name: Execute and apply cluster actions
|
|
needs: cluster
|
|
if: ${{ needs.cluster.result == 'success' && (inputs.mode == 'execute' || inputs.mode == 'autonomous') && !inputs.dry_run }}
|
|
runs-on: ${{ inputs.execution_runner }}
|
|
timeout-minutes: 45
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
CODEX_API_KEY: ${{ secrets.CODEX_API_KEY || secrets.OPENAI_API_KEY }}
|
|
CLOWNFISH_ALLOWED_OWNER: ${{ vars.CLOWNFISH_ALLOWED_OWNER || 'openclaw' }}
|
|
CLOWNFISH_ALLOW_EXECUTE: ${{ needs.cluster.outputs.allow_execute == '1' && '1' || '0' }}
|
|
CLOWNFISH_ALLOW_FIX_PR: ${{ needs.cluster.outputs.allow_fix_pr == '1' && '1' || '0' }}
|
|
CLOWNFISH_ALLOW_MERGE: ${{ needs.cluster.outputs.allow_merge || '0' }}
|
|
CLOWNFISH_CODEX_REASONING_EFFORT: ${{ vars.CLOWNFISH_CODEX_REASONING_EFFORT || 'medium' }}
|
|
CLOWNFISH_CODEX_REVIEW_ATTEMPTS: ${{ vars.CLOWNFISH_CODEX_REVIEW_ATTEMPTS || '2' }}
|
|
CLOWNFISH_REBASE_REPAIR_ATTEMPTS: ${{ vars.CLOWNFISH_REBASE_REPAIR_ATTEMPTS || '4' }}
|
|
CLOWNFISH_FIX_CODEX_TIMEOUT_MS: ${{ vars.CLOWNFISH_FIX_CODEX_TIMEOUT_MS || '1200000' }}
|
|
CLOWNFISH_FIX_STEP_TIMEOUT_MS: "1500000"
|
|
CLOWNFISH_FIX_TIMEOUT_RESERVE_MS: ${{ vars.CLOWNFISH_FIX_TIMEOUT_RESERVE_MS || '300000' }}
|
|
CLOWNFISH_FIX_PREFLIGHT_TIMEOUT_MS: ${{ vars.CLOWNFISH_FIX_PREFLIGHT_TIMEOUT_MS || '120000' }}
|
|
CLOWNFISH_RESOLVE_REVIEW_THREADS: ${{ vars.CLOWNFISH_RESOLVE_REVIEW_THREADS || '1' }}
|
|
CLOWNFISH_MODEL: ${{ inputs.model || vars.CLOWNFISH_MODEL || 'gpt-5.5' }}
|
|
CLOWNFISH_TARGET_VALIDATION_MODE: ${{ vars.CLOWNFISH_TARGET_VALIDATION_MODE || 'changed-only' }}
|
|
CLOWNFISH_POST_FLIGHT_IGNORE_CHECKS: ${{ vars.CLOWNFISH_POST_FLIGHT_IGNORE_CHECKS || 'auto-response,Labeler,Stale' }}
|
|
CLOWNFISH_MAX_ACTIVE_PRS_PER_AREA: ${{ vars.CLOWNFISH_MAX_ACTIVE_PRS_PER_AREA || '50' }}
|
|
CLOWNFISH_CLOSE_SUPERSEDED_SOURCE_PRS: ${{ vars.CLOWNFISH_CLOSE_SUPERSEDED_SOURCE_PRS || '0' }}
|
|
CLOWNFISH_GIT_USER_NAME: ${{ vars.CLOWNFISH_GIT_USER_NAME || 'projectclownfish' }}
|
|
CLOWNFISH_GIT_USER_EMAIL: ${{ vars.CLOWNFISH_GIT_USER_EMAIL || 'projectclownfish@users.noreply.github.com' }}
|
|
CODEX_CLI_VERSION: ${{ vars.CLOWNFISH_CODEX_CLI_VERSION || '0.125.0' }}
|
|
OPENCLAW_LOCAL_CHECK: "0"
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
|
|
- name: Create GitHub App token
|
|
id: app_token
|
|
continue-on-error: true
|
|
uses: actions/create-github-app-token@v3
|
|
with:
|
|
app-id: ${{ vars.CLOWNFISH_APP_ID }}
|
|
private-key: ${{ secrets.CLOWNFISH_APP_PRIVATE_KEY }}
|
|
owner: ${{ vars.CLOWNFISH_ALLOWED_OWNER || github.repository_owner }}
|
|
permission-contents: write
|
|
permission-issues: write
|
|
permission-pull-requests: write
|
|
|
|
- name: Create workflow-capable GitHub App token
|
|
id: workflow_app_token
|
|
continue-on-error: true
|
|
uses: actions/create-github-app-token@v3
|
|
with:
|
|
app-id: ${{ vars.CLOWNFISH_APP_ID }}
|
|
private-key: ${{ secrets.CLOWNFISH_APP_PRIVATE_KEY }}
|
|
owner: ${{ vars.CLOWNFISH_ALLOWED_OWNER || github.repository_owner }}
|
|
permission-contents: write
|
|
permission-issues: write
|
|
permission-pull-requests: write
|
|
permission-workflows: write
|
|
|
|
- uses: actions/setup-node@v5
|
|
with:
|
|
node-version: "24"
|
|
|
|
- name: Cache Codex CLI, npm, and target pnpm downloads
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/.npm
|
|
~/.cache/node/corepack
|
|
~/.local/share/pnpm/store
|
|
~/.projectclownfish/codex
|
|
key: ${{ runner.os }}-node24-codex-${{ env.CODEX_CLI_VERSION }}-target-pnpm-v1
|
|
restore-keys: |
|
|
${{ runner.os }}-node24-codex-${{ env.CODEX_CLI_VERSION }}-target-pnpm-
|
|
${{ runner.os }}-node24-codex-
|
|
|
|
- name: Install Codex CLI
|
|
run: |
|
|
set -euo pipefail
|
|
npm config set prefix "$HOME/.projectclownfish/codex"
|
|
npm config set cache "$HOME/.npm"
|
|
echo "$HOME/.projectclownfish/codex/bin" >> "$GITHUB_PATH"
|
|
export PATH="$HOME/.projectclownfish/codex/bin:$PATH"
|
|
|
|
if ! command -v codex >/dev/null 2>&1 || ! codex --version | grep -Fq "$CODEX_CLI_VERSION"; then
|
|
npm install -g "@openai/codex@$CODEX_CLI_VERSION" --prefer-offline --no-audit --no-fund
|
|
fi
|
|
codex --version
|
|
|
|
- name: Authenticate Codex
|
|
run: |
|
|
test -n "${OPENAI_API_KEY:-}"
|
|
test -n "${CODEX_API_KEY:-}"
|
|
printenv OPENAI_API_KEY | codex login --with-api-key >/dev/null
|
|
|
|
- name: Download worker artifacts
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: projectclownfish-worker-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: .projectclownfish/runs
|
|
|
|
- name: Validate job
|
|
run: npm run validate:job -- "${{ inputs.job }}"
|
|
|
|
- name: Execute credited fix artifact
|
|
if: ${{ env.CLOWNFISH_ALLOW_EXECUTE == '1' && env.CLOWNFISH_ALLOW_FIX_PR == '1' }}
|
|
timeout-minutes: 30
|
|
env:
|
|
GH_TOKEN: ${{ steps.workflow_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN || steps.app_token.outputs.token || github.token }}
|
|
run: npm run execute-fix -- "${{ inputs.job }}" --latest
|
|
|
|
- name: Apply safe closure actions
|
|
if: ${{ env.CLOWNFISH_ALLOW_EXECUTE == '1' }}
|
|
env:
|
|
GH_TOKEN: ${{ steps.workflow_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN || steps.app_token.outputs.token || github.token }}
|
|
run: npm run apply-result -- "${{ inputs.job }}" --latest
|
|
|
|
- name: Post-flight finalize fix PRs
|
|
if: ${{ env.CLOWNFISH_ALLOW_EXECUTE == '1' && env.CLOWNFISH_ALLOW_FIX_PR == '1' }}
|
|
env:
|
|
GH_TOKEN: ${{ steps.workflow_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN || steps.app_token.outputs.token || github.token }}
|
|
run: npm run post-flight -- "${{ inputs.job }}" --latest
|
|
|
|
- name: Apply post-flight closeouts
|
|
if: ${{ env.CLOWNFISH_ALLOW_EXECUTE == '1' }}
|
|
env:
|
|
GH_TOKEN: ${{ steps.workflow_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN || steps.app_token.outputs.token || github.token }}
|
|
run: npm run apply-result -- "${{ inputs.job }}" --latest
|
|
|
|
- name: Tag Clownfish targets
|
|
if: ${{ always() && env.CLOWNFISH_ALLOW_EXECUTE == '1' }}
|
|
env:
|
|
GH_TOKEN: ${{ steps.workflow_app_token.outputs.token || secrets.CLOWNFISH_GH_TOKEN || steps.app_token.outputs.token || github.token }}
|
|
run: npm run tag-clownfish -- .projectclownfish/runs --apply --live --open-branches false --report .projectclownfish/runs/clownfish-label-report.json
|
|
|
|
- name: Upload final worker artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: projectclownfish-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: |
|
|
.projectclownfish/runs/**
|
|
if-no-files-found: warn
|