clawsweeper/.github/workflows/commit-review.yml
2026-05-05 04:34:50 +01:00

548 lines
24 KiB
YAML

name: ClawSweeper Commit Review
run-name: Commit review ${{ github.event.inputs.target_repo || github.event.client_payload.target_repo || 'openclaw/openclaw' }}@${{ github.event.inputs.commit_sha || github.event.client_payload.after_sha || github.event.client_payload.commit_sha || '?' }}
on:
repository_dispatch:
types: [clawsweeper_commit_review]
workflow_dispatch:
inputs:
enabled:
description: "Run commit review"
required: false
default: "true"
target_repo:
description: "Repository containing the main commit"
required: false
default: "openclaw/openclaw"
commit_sha:
description: "Specific commit SHA to review"
required: true
before_sha:
description: "Optional base SHA; defaults to the commit's first parent"
required: false
default: ""
commit_offset:
description: "Internal offset for continuation runs"
required: false
default: "0"
create_checks:
description: "Create/update GitHub Checks on reviewed commits"
required: false
default: ""
additional_prompt:
description: "Optional extra review instruction appended to the prompt"
required: false
default: ""
permissions:
contents: write
actions: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
CLAWSWEEPER_APP_CLIENT_ID: Iv23liOECG0slfuhz093
concurrency:
group: commit-review-${{ github.event.inputs.target_repo || github.event.client_payload.target_repo || 'openclaw/openclaw' }}-${{ github.event.inputs.commit_sha || github.event.client_payload.after_sha || github.event.client_payload.commit_sha || github.run_id }}
cancel-in-progress: false
jobs:
disabled-target:
name: Skip disabled target commit review
if: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.target_repo == 'openclaw/clawhub' && vars.CLAWSWEEPER_ENABLE_CLAWHUB != '1' }}
runs-on: ubuntu-latest
steps:
- name: Explain skipped commit review
run: |
echo "Skipping commit review for ${{ github.event.client_payload.target_repo }}."
echo "Set CLAWSWEEPER_ENABLE_CLAWHUB=1 after installing the ClawSweeper GitHub App on openclaw/clawhub."
plan:
name: Plan commits
if: ${{ !(github.event_name == 'repository_dispatch' && github.event.client_payload.target_repo == 'openclaw/clawhub' && vars.CLAWSWEEPER_ENABLE_CLAWHUB != '1') }}
runs-on: ubuntu-latest
timeout-minutes: 40
outputs:
create_checks: ${{ steps.mode.outputs.create_checks }}
enabled: ${{ steps.mode.outputs.enabled }}
has_more: ${{ steps.select.outputs.has_more }}
matrix: ${{ steps.select.outputs.matrix }}
next_offset: ${{ steps.select.outputs.next_offset }}
planned_count: ${{ steps.select.outputs.planned_count }}
skipped_count: ${{ steps.select.outputs.skipped_count }}
target_checkout_dir: ${{ steps.target.outputs.target_checkout_dir }}
target_repo: ${{ steps.target.outputs.target_repo }}
target_repo_name: ${{ steps.target.outputs.target_repo_name }}
target_repo_owner: ${{ steps.target.outputs.target_repo_owner }}
steps:
- uses: actions/checkout@v6
with:
path: clawsweeper
filter: blob:none
fetch-depth: 1
- name: Resolve mode
id: mode
run: |
enabled="${{ github.event.inputs.enabled || github.event.client_payload.enabled || 'true' }}"
create_checks="${{ github.event.inputs.create_checks || github.event.client_payload.create_checks || vars.CLAWSWEEPER_COMMIT_REVIEW_CREATE_CHECKS || 'false' }}"
case "$enabled" in
false|FALSE|0|no|NO|off|OFF)
echo "Commit review disabled by input/payload."
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "create_checks=false" >> "$GITHUB_OUTPUT"
exit 0
;;
esac
case "$create_checks" in
true|TRUE|1|yes|YES|on|ON) create_checks=true ;;
*) create_checks=false ;;
esac
echo "enabled=true" >> "$GITHUB_OUTPUT"
echo "create_checks=$create_checks" >> "$GITHUB_OUTPUT"
- name: Resolve target repository
id: target
run: |
target_repo="${{ github.event.inputs.target_repo || github.event.client_payload.target_repo || 'openclaw/openclaw' }}"
if ! printf '%s' "$target_repo" | grep -Eq '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$'; then
echo "Invalid target_repo: $target_repo" >&2
exit 1
fi
target_owner="${target_repo%%/*}"
target_name="${target_repo#*/}"
{
echo "target_repo=$target_repo"
echo "target_repo_owner=$target_owner"
echo "target_repo_name=$target_name"
echo "target_checkout_dir=target-$target_name"
} >> "$GITHUB_OUTPUT"
- name: Create target read token
id: target-read-token
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
owner: ${{ steps.target.outputs.target_repo_owner }}
repositories: ${{ steps.target.outputs.target_repo_name }}
permission-actions: read
permission-contents: read
permission-issues: read
permission-pull-requests: read
- name: Use state token
id: state-token
run: echo "token=${{ secrets.CLAWSWEEPER_STATE_REPOSITORY_TOKEN }}" >> "$GITHUB_OUTPUT"
- uses: ./clawsweeper/.github/actions/setup-state
with:
token: ${{ steps.state-token.outputs.token }}
worktree-path: clawsweeper
fetch-depth: 1
- uses: ./clawsweeper/.github/actions/setup-pnpm
with:
working-directory: clawsweeper
build-script: build:all
- name: Select commits
id: select
env:
GH_TOKEN: ${{ github.token }}
TARGET_REPO: ${{ steps.target.outputs.target_repo }}
TARGET_NAME: ${{ steps.target.outputs.target_checkout_dir }}
TARGET_TOKEN: ${{ steps.target-read-token.outputs.token }}
COMMIT_SHA: ${{ github.event.inputs.commit_sha || github.event.client_payload.after_sha || github.event.client_payload.commit_sha || '' }}
BEFORE_SHA: ${{ github.event.inputs.before_sha || github.event.client_payload.before_sha || '' }}
COMMIT_OFFSET: ${{ github.event.inputs.commit_offset || github.event.client_payload.commit_offset || '0' }}
ENABLED: ${{ steps.mode.outputs.enabled }}
SOURCE_REF: ${{ github.event.client_payload.ref || 'refs/heads/main' }}
SETTLE_SECONDS: ${{ vars.CLAWSWEEPER_COMMIT_REVIEW_SETTLE_SECONDS || '60' }}
PAGE_SIZE: ${{ vars.CLAWSWEEPER_COMMIT_REVIEW_PAGE_SIZE || '' }}
run: |
set -euo pipefail
active_run_count() {
gh run list --repo "${{ github.repository }}" --limit 100 --json workflowName,status 2>/dev/null \
| WORKFLOW_NAME="$1" jq '[.[] | select(.workflowName == env.WORKFLOW_NAME) | select(.status == "in_progress" or .status == "pending" or .status == "queued" or .status == "waiting" or .status == "requested")] | length' 2>/dev/null \
|| printf '0'
}
active_sweep_background_workers() {
local normal_limit hot_limit
normal_limit="$(pnpm --dir clawsweeper run --silent workflow -- limit review_shards.normal_default)"
hot_limit="$(pnpm --dir clawsweeper run --silent workflow -- limit review_shards.hot_intake_default)"
gh run list --repo "${{ github.repository }}" --limit 100 --json workflowName,displayTitle,status 2>/dev/null \
| NORMAL_LIMIT="$normal_limit" HOT_LIMIT="$hot_limit" jq '[.[] | select(.workflowName == "ClawSweeper") | select(.status == "in_progress" or .status == "pending" or .status == "queued" or .status == "waiting" or .status == "requested") | if .displayTitle == "Review ClawSweeper items" then (env.NORMAL_LIMIT | tonumber) elif .displayTitle == "Review hot ClawSweeper items" then (env.HOT_LIMIT | tonumber) else 0 end] | add // 0' 2>/dev/null \
|| printf '0'
}
active_sweep_exact_count() {
gh run list --repo "${{ github.repository }}" --limit 100 --json workflowName,displayTitle,status 2>/dev/null \
| jq '[.[] | select(.workflowName == "ClawSweeper") | select(.status == "in_progress" or .status == "pending" or .status == "queued" or .status == "waiting" or .status == "requested") | select(.displayTitle | startswith("Review event item "))] | length' 2>/dev/null \
|| printf '0'
}
if [ -z "$PAGE_SIZE" ]; then
active_critical_workers="$(( $(active_run_count "repair cluster worker") + $(active_sweep_exact_count) ))"
active_background_workers="$(active_sweep_background_workers)"
PAGE_SIZE="$(pnpm --dir clawsweeper run --silent workflow -- worker-limit commit_review --active-critical "$active_critical_workers" --active-background "$active_background_workers")"
fi
page_size_hard_cap="$(pnpm --dir clawsweeper run --silent workflow -- limit commit_review.page_size_hard_cap)"
if [ "$ENABLED" = "false" ]; then
{
echo "matrix=[]"
echo "has_more=false"
echo "next_offset=0"
echo "planned_count=0"
echo "skipped_count=0"
} >> "$GITHUB_OUTPUT"
exit 0
fi
if ! printf '%s' "$COMMIT_OFFSET" | grep -Eq '^[0-9]+$'; then
echo "Invalid commit offset: $COMMIT_OFFSET" >&2
exit 1
fi
if [ "$SOURCE_REF" != "refs/heads/main" ]; then
echo "Commit review only accepts main branch pushes; got $SOURCE_REF" >&2
exit 1
fi
if [ -z "$COMMIT_SHA" ]; then
echo "Missing commit SHA." >&2
exit 1
fi
if ! printf '%s' "$COMMIT_SHA" | grep -Eq '^[0-9a-fA-F]{40}$'; then
echo "Invalid commit SHA: $COMMIT_SHA" >&2
exit 1
fi
if [ -n "$BEFORE_SHA" ] && ! printf '%s' "$BEFORE_SHA" | grep -Eq '^(0{40}|[0-9a-fA-F]{40})$'; then
echo "Invalid before SHA: $BEFORE_SHA" >&2
exit 1
fi
if ! printf '%s' "$SETTLE_SECONDS" | grep -Eq '^[0-9]+$'; then
echo "Invalid settle wait: $SETTLE_SECONDS" >&2
exit 1
fi
if ! printf '%s' "$PAGE_SIZE" | grep -Eq '^[0-9]+$'; then
echo "Invalid commit review page size: $PAGE_SIZE" >&2
exit 1
fi
if [ "$PAGE_SIZE" -lt 1 ]; then
PAGE_SIZE=1
fi
if [ "$PAGE_SIZE" -gt "$page_size_hard_cap" ]; then
PAGE_SIZE="$page_size_hard_cap"
fi
if [ "$SETTLE_SECONDS" -gt 0 ]; then
echo "Waiting ${SETTLE_SECONDS}s for target main to settle before selecting commits."
sleep "$SETTLE_SECONDS"
fi
url="https://x-access-token:${TARGET_TOKEN}@github.com/${TARGET_REPO}.git"
git clone --filter=blob:none --no-checkout "$url" "$TARGET_NAME"
git -C "$TARGET_NAME" fetch --no-tags origin main --depth=1000
git -C "$TARGET_NAME" fetch --no-tags origin "$COMMIT_SHA" --depth=200
if ! git -C "$TARGET_NAME" merge-base --is-ancestor "$COMMIT_SHA" refs/remotes/origin/main; then
echo "Commit $COMMIT_SHA is not reachable from origin/main in the fetched history." >&2
exit 1
fi
if [ -n "$BEFORE_SHA" ] && ! printf '%s' "$BEFORE_SHA" | grep -Eq '^0{40}$'; then
git -C "$TARGET_NAME" fetch --no-tags origin "$BEFORE_SHA" --depth=200 || true
commits="$(git -C "$TARGET_NAME" rev-list --reverse "$BEFORE_SHA..$COMMIT_SHA" || true)"
else
commits="$COMMIT_SHA"
fi
if [ -z "$commits" ]; then
commits="$COMMIT_SHA"
fi
total_count="$(printf '%s\n' "$commits" | sed '/^$/d' | wc -l | tr -d ' ')"
page_size="$PAGE_SIZE"
start_line=$((COMMIT_OFFSET + 1))
end_line=$((COMMIT_OFFSET + page_size))
selected_commits="$(printf '%s\n' "$commits" | sed -n "${start_line},${end_line}p")"
selected_count="$(printf '%s\n' "$selected_commits" | sed '/^$/d' | wc -l | tr -d ' ')"
next_offset=$((COMMIT_OFFSET + selected_count))
has_more=false
if [ "$next_offset" -lt "$total_count" ]; then
has_more=true
fi
node clawsweeper/dist/commit-sweeper.js classify \
--target-repo "$TARGET_REPO" \
--target-dir "$TARGET_NAME" \
--commit-shas "$selected_commits" \
--artifact-dir skipped-commit-artifacts > classify.json
cat classify.json
node clawsweeper/dist/repair/workflow-utils.js classify-output \
--classify classify.json \
--has-more "$has_more" \
--next-offset "$next_offset" >> "$GITHUB_OUTPUT"
echo "Selected $selected_count/$total_count commits at offset $COMMIT_OFFSET"
printf 'Selected commits:\n%s\n' "$selected_commits"
- uses: actions/upload-artifact@v7
if: always()
with:
name: skipped-commit-reports
path: skipped-commit-artifacts
if-no-files-found: ignore
review:
name: Review commit ${{ matrix.sha }}
needs: plan
if: ${{ needs.plan.outputs.planned_count != '0' }}
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.plan.outputs.matrix) }}
steps:
- uses: actions/checkout@v6
with:
path: clawsweeper
filter: blob:none
fetch-depth: 1
persist-credentials: false
- name: Create target read token
id: target-read-token
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
owner: ${{ needs.plan.outputs.target_repo_owner }}
repositories: ${{ needs.plan.outputs.target_repo_name }}
permission-actions: read
permission-contents: read
permission-issues: read
permission-pull-requests: read
- name: Use state token
id: state-token
run: echo "token=${{ secrets.CLAWSWEEPER_STATE_REPOSITORY_TOKEN }}" >> "$GITHUB_OUTPUT"
- uses: ./clawsweeper/.github/actions/setup-state
with:
token: ${{ steps.state-token.outputs.token }}
worktree-path: clawsweeper
fetch-depth: 1
- uses: ./clawsweeper/.github/actions/setup-pnpm
with:
working-directory: clawsweeper
build-script: build
- uses: ./clawsweeper/.github/actions/setup-codex
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
with:
login-status: "true"
- name: Check out target main
env:
TARGET_REPO: ${{ needs.plan.outputs.target_repo }}
TARGET_NAME: ${{ needs.plan.outputs.target_checkout_dir }}
TARGET_TOKEN: ${{ steps.target-read-token.outputs.token }}
COMMIT_SHA: ${{ matrix.sha }}
run: |
set -euo pipefail
url="https://x-access-token:${TARGET_TOKEN}@github.com/${TARGET_REPO}.git"
git clone --filter=blob:none --no-checkout "$url" "$TARGET_NAME"
git -C "$TARGET_NAME" fetch --no-tags origin main --depth=1000
git -C "$TARGET_NAME" checkout -B main refs/remotes/origin/main
git -C "$TARGET_NAME" fetch --no-tags origin "$COMMIT_SHA" --depth=200
if ! git -C "$TARGET_NAME" merge-base --is-ancestor "$COMMIT_SHA" HEAD; then
echo "Commit $COMMIT_SHA is not reachable from the checked-out origin/main history." >&2
exit 1
fi
if ! git -C "$TARGET_NAME" rev-parse --verify "$COMMIT_SHA^" >/dev/null 2>&1; then
git -C "$TARGET_NAME" fetch --deepen=500 origin main || true
fi
echo "Target main head: $(git -C "$TARGET_NAME" rev-parse --short HEAD)"
echo "Commit under review: $(git -C "$TARGET_NAME" rev-parse --short "$COMMIT_SHA")"
- name: Review commit
working-directory: clawsweeper
env:
GH_TOKEN: ${{ steps.target-read-token.outputs.token }}
COMMIT_SWEEPER_TARGET_GH_TOKEN: ${{ steps.target-read-token.outputs.token }}
COMMIT_SWEEPER_ADDITIONAL_PROMPT: ${{ github.event.inputs.additional_prompt || github.event.client_payload.additional_prompt || '' }}
run: |
node dist/commit-sweeper.js review \
--target-repo "${{ needs.plan.outputs.target_repo }}" \
--target-dir "../${{ needs.plan.outputs.target_checkout_dir }}" \
--commit-sha "${{ matrix.sha }}" \
--report-dir ../commit-artifacts \
--artifact-mode \
--work-dir ../commit-work \
--codex-model gpt-5.5 \
--codex-reasoning-effort high \
--codex-sandbox danger-full-access \
--codex-timeout-ms 1800000
- name: Create target checks token
id: target-checks-token
if: ${{ needs.plan.outputs.create_checks == 'true' }}
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
owner: ${{ needs.plan.outputs.target_repo_owner }}
repositories: ${{ needs.plan.outputs.target_repo_name }}
permission-checks: write
- name: Publish commit check
if: ${{ needs.plan.outputs.create_checks == 'true' }}
working-directory: clawsweeper
env:
GH_TOKEN: ${{ steps.target-checks-token.outputs.token }}
run: |
set -euo pipefail
test -n "$GH_TOKEN"
report_path="$(find ../commit-artifacts -name '${{ matrix.sha }}.md' -print -quit)"
if [ -z "$report_path" ]; then
echo "Missing report artifact for ${{ matrix.sha }}" >&2
exit 1
fi
relative_path="records/${report_path#../commit-artifacts/}"
node dist/commit-sweeper.js publish-check \
--target-repo "${{ needs.plan.outputs.target_repo }}" \
--commit-sha "${{ matrix.sha }}" \
--report-path "$report_path" \
--report-relative-path "$relative_path" \
--report-repo "${{ github.repository }}"
- uses: actions/upload-artifact@v7
if: always()
with:
name: commit-review-${{ matrix.sha }}
path: commit-artifacts
if-no-files-found: ignore
publish:
name: Commit reports
needs: [plan, review]
if: ${{ always() && (needs.plan.outputs.planned_count != '0' || needs.plan.outputs.skipped_count != '0') }}
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
with:
filter: blob:none
fetch-depth: 0
- name: Use state token
id: state-token
run: echo "token=${{ secrets.CLAWSWEEPER_STATE_REPOSITORY_TOKEN }}" >> "$GITHUB_OUTPUT"
- uses: ./.github/actions/setup-state
with:
token: ${{ steps.state-token.outputs.token }}
- uses: ./.github/actions/setup-pnpm
with:
build-script: build:all
- uses: actions/download-artifact@v8
if: ${{ needs.plan.outputs.planned_count != '0' }}
with:
pattern: commit-review-*
path: commit-artifacts
merge-multiple: true
- uses: actions/download-artifact@v8
if: ${{ needs.plan.outputs.skipped_count != '0' }}
with:
name: skipped-commit-reports
path: commit-artifacts
- name: Commit reports
run: |
set -euo pipefail
git pull --rebase
node dist/commit-sweeper.js copy-artifacts --artifact-dir commit-artifacts --records-dir records
pnpm run repair:publish-main -- \
--message "chore: update commit review reports" \
--path records \
--rebase-strategy theirs
- name: Create repair dispatch token
id: clawsweeper-token
if: ${{ vars.CLAWSWEEPER_COMMIT_FINDINGS_ENABLED != 'false' }}
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
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: Dispatch commit findings to repair lane
if: ${{ vars.CLAWSWEEPER_COMMIT_FINDINGS_ENABLED != 'false' }}
env:
GH_TOKEN: ${{ steps.clawsweeper-token.outputs.token }}
ENABLED: ${{ vars.CLAWSWEEPER_COMMIT_FINDINGS_ENABLED || 'true' }}
run: |
set -euo pipefail
node dist/commit-sweeper.js dispatch-findings \
--artifact-dir commit-artifacts \
--repair-repo openclaw/clawsweeper \
--repair-workflow repair-commit-finding-intake.yml \
--dispatch-mode workflow_dispatch \
--report-repo "${{ github.repository }}" \
--enabled "$ENABLED"
- name: Create target checks token
id: target-checks-token
if: ${{ needs.plan.outputs.create_checks == 'true' }}
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
owner: ${{ needs.plan.outputs.target_repo_owner }}
repositories: ${{ needs.plan.outputs.target_repo_name }}
permission-checks: write
- name: Publish commit checks
if: ${{ needs.plan.outputs.create_checks == 'true' }}
env:
GH_TOKEN: ${{ steps.target-checks-token.outputs.token }}
run: |
set -euo pipefail
test -n "$GH_TOKEN"
find commit-artifacts -name '*.md' -print0 | while IFS= read -r -d '' report_path; do
sha="$(basename "$report_path" .md)"
relative_path="records/${report_path#commit-artifacts/}"
node dist/commit-sweeper.js publish-check \
--target-repo "${{ needs.plan.outputs.target_repo }}" \
--commit-sha "$sha" \
--report-path "$report_path" \
--report-relative-path "$relative_path" \
--report-repo "${{ github.repository }}"
done
- name: Continue commit review range
if: ${{ success() && needs.plan.outputs.has_more == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
TARGET_REPO: ${{ needs.plan.outputs.target_repo }}
BEFORE_SHA: ${{ github.event.inputs.before_sha || github.event.client_payload.before_sha || '' }}
AFTER_SHA: ${{ github.event.inputs.commit_sha || github.event.client_payload.after_sha || github.event.client_payload.commit_sha || '' }}
NEXT_OFFSET: ${{ needs.plan.outputs.next_offset }}
CREATE_CHECKS: ${{ needs.plan.outputs.create_checks }}
ADDITIONAL_PROMPT: ${{ github.event.inputs.additional_prompt || github.event.client_payload.additional_prompt || '' }}
run: |
set -euo pipefail
payload="$(jq -nc \
--arg target_repo "$TARGET_REPO" \
--arg before_sha "$BEFORE_SHA" \
--arg after_sha "$AFTER_SHA" \
--arg ref "refs/heads/main" \
--arg additional_prompt "$ADDITIONAL_PROMPT" \
--argjson commit_offset "$NEXT_OFFSET" \
--argjson create_checks "$CREATE_CHECKS" \
'{event_type:"clawsweeper_commit_review",client_payload:{target_repo:$target_repo,before_sha:$before_sha,after_sha:$after_sha,ref:$ref,commit_offset:$commit_offset,enabled:true,create_checks:$create_checks,additional_prompt:$additional_prompt}}')"
gh api repos/${{ github.repository }}/dispatches \
--method POST \
--input - <<< "$payload"