mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
ci: make release child dispatch idempotent (#102858)
This commit is contained in:
@@ -18,6 +18,11 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
dispatch_id:
|
||||
description: Optional parent workflow dispatch identifier
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
@@ -31,7 +36,7 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
run-name: ${{ github.event_name == 'workflow_dispatch' && inputs.release_gate && format('CI release gate {0}', inputs.target_ref) || 'CI' }}
|
||||
run-name: ${{ github.event_name == 'workflow_dispatch' && inputs.dispatch_id != '' && format('CI {0}', inputs.dispatch_id) || (github.event_name == 'workflow_dispatch' && inputs.release_gate && format('CI release gate {0}', inputs.target_ref) || 'CI') }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.event_name == 'workflow_dispatch' && format('{0}-manual-v1-{1}', github.workflow, github.run_id) || (github.event_name == 'pull_request' && format('{0}-v7-{1}', github.workflow, github.event.pull_request.number) || (github.repository == 'openclaw/openclaw' && format('{0}-v7-{1}', github.workflow, github.ref) || format('{0}-v7-{1}-{2}', github.workflow, github.ref, github.sha))) }}
|
||||
|
||||
@@ -275,9 +275,10 @@ jobs:
|
||||
|
||||
dispatch_and_wait() {
|
||||
local workflow="$1"
|
||||
shift
|
||||
local dispatch_run_name="$2"
|
||||
shift 2
|
||||
|
||||
local dispatch_output run_id status conclusion url poll_count
|
||||
local dispatch_output dispatch_status matches_json match_count run_id status conclusion url poll_count
|
||||
gh_with_retry() {
|
||||
local output status attempt
|
||||
for attempt in 1 2 3 4 5 6; do
|
||||
@@ -300,18 +301,42 @@ jobs:
|
||||
printf '%s\n' "$output" >&2
|
||||
return "$status"
|
||||
}
|
||||
dispatch_output="$(gh_with_retry workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@")"
|
||||
# A failed dispatch POST can still create a run. Never retry it; recover only by exact run name.
|
||||
set +e
|
||||
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
||||
dispatch_status=$?
|
||||
set -e
|
||||
printf '%s\n' "$dispatch_output"
|
||||
run_id="$(
|
||||
printf '%s\n' "$dispatch_output" |
|
||||
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
||||
tail -n 1
|
||||
)"
|
||||
|
||||
run_id=""
|
||||
for _ in $(seq 1 60); do
|
||||
if matches_json="$(
|
||||
DISPATCH_RUN_NAME="$dispatch_run_name" CHILD_WORKFLOW_REF="$CHILD_WORKFLOW_REF" \
|
||||
gh_with_retry api -X GET "repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow}/runs" \
|
||||
-F event=workflow_dispatch \
|
||||
-F per_page=100 \
|
||||
--jq '[.workflow_runs[] | select(.display_title == env.DISPATCH_RUN_NAME and .head_branch == env.CHILD_WORKFLOW_REF) | .id]'
|
||||
)"; then
|
||||
match_count="$(jq 'length' <<< "$matches_json")"
|
||||
if (( match_count > 1 )); then
|
||||
echo "::error::Multiple runs matched ${dispatch_run_name}; refusing to guess." >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( match_count == 1 )); then
|
||||
run_id="$(jq -r '.[0]' <<< "$matches_json")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
echo "::error::gh workflow run ${workflow} did not return an Actions run URL; refusing to guess from recent workflow_dispatch runs." >&2
|
||||
echo "::error::Could not find exact dispatched run ${dispatch_run_name}; dispatch status ${dispatch_status}. The dispatch was not retried to avoid creating a duplicate child." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$dispatch_status" -ne 0 ]]; then
|
||||
echo "::warning::${workflow} dispatch returned status ${dispatch_status}; adopted exact run ${run_id}." >&2
|
||||
fi
|
||||
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
@@ -383,7 +408,9 @@ jobs:
|
||||
echo "- Target SHA: \`${TARGET_SHA}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
dispatch_and_wait ci.yml -f target_ref="$TARGET_SHA" -f include_android=true
|
||||
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-ci"
|
||||
dispatch_run_name="CI ${dispatch_id}"
|
||||
dispatch_and_wait ci.yml "$dispatch_run_name" -f target_ref="$TARGET_SHA" -f include_android=true -f dispatch_id="$dispatch_id"
|
||||
|
||||
plugin_prerelease:
|
||||
name: Run plugin prerelease validation
|
||||
@@ -408,9 +435,10 @@ jobs:
|
||||
|
||||
dispatch_and_wait() {
|
||||
local workflow="$1"
|
||||
shift
|
||||
local dispatch_run_name="$2"
|
||||
shift 2
|
||||
|
||||
local dispatch_output run_id status conclusion url poll_count
|
||||
local dispatch_output dispatch_status matches_json match_count run_id status conclusion url poll_count
|
||||
gh_with_retry() {
|
||||
local output status attempt
|
||||
for attempt in 1 2 3 4 5 6; do
|
||||
@@ -433,18 +461,42 @@ jobs:
|
||||
printf '%s\n' "$output" >&2
|
||||
return "$status"
|
||||
}
|
||||
dispatch_output="$(gh_with_retry workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@")"
|
||||
# A failed dispatch POST can still create a run. Never retry it; recover only by exact run name.
|
||||
set +e
|
||||
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
||||
dispatch_status=$?
|
||||
set -e
|
||||
printf '%s\n' "$dispatch_output"
|
||||
run_id="$(
|
||||
printf '%s\n' "$dispatch_output" |
|
||||
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
||||
tail -n 1
|
||||
)"
|
||||
|
||||
run_id=""
|
||||
for _ in $(seq 1 60); do
|
||||
if matches_json="$(
|
||||
DISPATCH_RUN_NAME="$dispatch_run_name" CHILD_WORKFLOW_REF="$CHILD_WORKFLOW_REF" \
|
||||
gh_with_retry api -X GET "repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow}/runs" \
|
||||
-F event=workflow_dispatch \
|
||||
-F per_page=100 \
|
||||
--jq '[.workflow_runs[] | select(.display_title == env.DISPATCH_RUN_NAME and .head_branch == env.CHILD_WORKFLOW_REF) | .id]'
|
||||
)"; then
|
||||
match_count="$(jq 'length' <<< "$matches_json")"
|
||||
if (( match_count > 1 )); then
|
||||
echo "::error::Multiple runs matched ${dispatch_run_name}; refusing to guess." >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( match_count == 1 )); then
|
||||
run_id="$(jq -r '.[0]' <<< "$matches_json")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
echo "::error::gh workflow run ${workflow} did not return an Actions run URL; refusing to guess from recent workflow_dispatch runs." >&2
|
||||
echo "::error::Could not find exact dispatched run ${dispatch_run_name}; dispatch status ${dispatch_status}. The dispatch was not retried to avoid creating a duplicate child." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$dispatch_status" -ne 0 ]]; then
|
||||
echo "::warning::${workflow} dispatch returned status ${dispatch_status}; adopted exact run ${run_id}." >&2
|
||||
fi
|
||||
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
@@ -516,7 +568,9 @@ jobs:
|
||||
echo "- Target SHA: \`${TARGET_SHA}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
dispatch_and_wait plugin-prerelease.yml -f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true
|
||||
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-plugin-prerelease"
|
||||
dispatch_run_name="Plugin Prerelease ${dispatch_id}"
|
||||
dispatch_and_wait plugin-prerelease.yml "$dispatch_run_name" -f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true -f dispatch_id="$dispatch_id"
|
||||
|
||||
release_checks:
|
||||
name: Run release/live/Docker/QA validation
|
||||
@@ -551,9 +605,10 @@ jobs:
|
||||
|
||||
dispatch_and_wait() {
|
||||
local workflow="$1"
|
||||
shift
|
||||
local dispatch_run_name="$2"
|
||||
shift 2
|
||||
|
||||
local dispatch_output run_id status conclusion url poll_count run_json
|
||||
local dispatch_output dispatch_status matches_json match_count run_id status conclusion url poll_count run_json
|
||||
gh_with_retry() {
|
||||
local output status attempt
|
||||
for attempt in 1 2 3 4 5 6; do
|
||||
@@ -576,18 +631,42 @@ jobs:
|
||||
printf '%s\n' "$output" >&2
|
||||
return "$status"
|
||||
}
|
||||
dispatch_output="$(gh_with_retry workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@")"
|
||||
# A failed dispatch POST can still create a run. Never retry it; recover only by exact run name.
|
||||
set +e
|
||||
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
||||
dispatch_status=$?
|
||||
set -e
|
||||
printf '%s\n' "$dispatch_output"
|
||||
run_id="$(
|
||||
printf '%s\n' "$dispatch_output" |
|
||||
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
||||
tail -n 1
|
||||
)"
|
||||
|
||||
run_id=""
|
||||
for _ in $(seq 1 60); do
|
||||
if matches_json="$(
|
||||
DISPATCH_RUN_NAME="$dispatch_run_name" CHILD_WORKFLOW_REF="$CHILD_WORKFLOW_REF" \
|
||||
gh_with_retry api -X GET "repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow}/runs" \
|
||||
-F event=workflow_dispatch \
|
||||
-F per_page=100 \
|
||||
--jq '[.workflow_runs[] | select(.display_title == env.DISPATCH_RUN_NAME and .head_branch == env.CHILD_WORKFLOW_REF) | .id]'
|
||||
)"; then
|
||||
match_count="$(jq 'length' <<< "$matches_json")"
|
||||
if (( match_count > 1 )); then
|
||||
echo "::error::Multiple runs matched ${dispatch_run_name}; refusing to guess." >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( match_count == 1 )); then
|
||||
run_id="$(jq -r '.[0]' <<< "$matches_json")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
echo "::error::gh workflow run ${workflow} did not return an Actions run URL; refusing to guess from recent workflow_dispatch runs." >&2
|
||||
echo "::error::Could not find exact dispatched run ${dispatch_run_name}; dispatch status ${dispatch_status}. The dispatch was not retried to avoid creating a duplicate child." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$dispatch_status" -ne 0 ]]; then
|
||||
echo "::warning::${workflow} dispatch returned status ${dispatch_status}; adopted exact run ${run_id}." >&2
|
||||
fi
|
||||
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
@@ -764,7 +843,10 @@ jobs:
|
||||
args+=(-f codex_plugin_spec="$CODEX_PLUGIN_SPEC")
|
||||
fi
|
||||
|
||||
dispatch_and_wait openclaw-release-checks.yml "${args[@]}"
|
||||
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-release-checks"
|
||||
dispatch_run_name="OpenClaw Release Checks ${dispatch_id}"
|
||||
args+=(-f dispatch_id="$dispatch_id")
|
||||
dispatch_and_wait openclaw-release-checks.yml "$dispatch_run_name" "${args[@]}"
|
||||
|
||||
npm_telegram:
|
||||
name: Run package Telegram E2E
|
||||
@@ -818,18 +900,46 @@ jobs:
|
||||
args+=(-f scenario="$SCENARIO")
|
||||
fi
|
||||
|
||||
dispatch_output="$(gh_with_retry workflow run npm-telegram-beta-e2e.yml --ref "$CHILD_WORKFLOW_REF" "${args[@]}")"
|
||||
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-npm-telegram"
|
||||
dispatch_run_name="NPM Telegram Beta E2E ${dispatch_id}"
|
||||
args+=(-f dispatch_id="$dispatch_id")
|
||||
|
||||
# A failed dispatch POST can still create a run. Never retry it; recover only by exact run name.
|
||||
set +e
|
||||
dispatch_output="$(gh workflow run npm-telegram-beta-e2e.yml --ref "$CHILD_WORKFLOW_REF" "${args[@]}" 2>&1)"
|
||||
dispatch_status=$?
|
||||
set -e
|
||||
printf '%s\n' "$dispatch_output"
|
||||
run_id="$(
|
||||
printf '%s\n' "$dispatch_output" |
|
||||
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
||||
tail -n 1
|
||||
)"
|
||||
|
||||
run_id=""
|
||||
for _ in $(seq 1 60); do
|
||||
if matches_json="$(
|
||||
DISPATCH_RUN_NAME="$dispatch_run_name" CHILD_WORKFLOW_REF="$CHILD_WORKFLOW_REF" \
|
||||
gh_with_retry api -X GET "repos/${GITHUB_REPOSITORY}/actions/workflows/npm-telegram-beta-e2e.yml/runs" \
|
||||
-F event=workflow_dispatch \
|
||||
-F per_page=100 \
|
||||
--jq '[.workflow_runs[] | select(.display_title == env.DISPATCH_RUN_NAME and .head_branch == env.CHILD_WORKFLOW_REF) | .id]'
|
||||
)"; then
|
||||
match_count="$(jq 'length' <<< "$matches_json")"
|
||||
if (( match_count > 1 )); then
|
||||
echo "::error::Multiple runs matched ${dispatch_run_name}; refusing to guess." >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( match_count == 1 )); then
|
||||
run_id="$(jq -r '.[0]' <<< "$matches_json")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
echo "::error::gh workflow run npm-telegram-beta-e2e.yml did not return an Actions run URL; refusing to guess from recent workflow_dispatch runs." >&2
|
||||
echo "::error::Could not find exact dispatched run ${dispatch_run_name}; dispatch status ${dispatch_status}. The dispatch was not retried to avoid creating a duplicate child." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$dispatch_status" -ne 0 ]]; then
|
||||
echo "::warning::npm-telegram-beta-e2e.yml dispatch returned status ${dispatch_status}; adopted exact run ${run_id}." >&2
|
||||
fi
|
||||
|
||||
echo "Dispatched npm-telegram-beta-e2e.yml: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
@@ -942,7 +1052,9 @@ jobs:
|
||||
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
dispatch_run_name="OpenClaw Performance ${dispatch_id}"
|
||||
|
||||
dispatch_output="$(gh_with_retry workflow run openclaw-performance.yml \
|
||||
# A failed dispatch POST can still create a run. Never retry it; recover only by exact run name.
|
||||
set +e
|
||||
dispatch_output="$(gh workflow run openclaw-performance.yml \
|
||||
--ref "$CHILD_WORKFLOW_REF" \
|
||||
-f target_ref="$TARGET_SHA" \
|
||||
-f profile=release \
|
||||
@@ -950,27 +1062,40 @@ jobs:
|
||||
-f deep_profile=false \
|
||||
-f live_openai_candidate=false \
|
||||
-f fail_on_regression=true \
|
||||
-f dispatch_id="$dispatch_id")"
|
||||
-f dispatch_id="$dispatch_id" 2>&1)"
|
||||
dispatch_status=$?
|
||||
set -e
|
||||
printf '%s\n' "$dispatch_output"
|
||||
|
||||
run_id=""
|
||||
for _ in $(seq 1 60); do
|
||||
run_id="$(
|
||||
DISPATCH_RUN_NAME="$dispatch_run_name" gh_with_retry api -X GET "repos/${GITHUB_REPOSITORY}/actions/workflows/openclaw-performance.yml/runs" \
|
||||
if matches_json="$(
|
||||
DISPATCH_RUN_NAME="$dispatch_run_name" CHILD_WORKFLOW_REF="$CHILD_WORKFLOW_REF" \
|
||||
gh_with_retry api -X GET "repos/${GITHUB_REPOSITORY}/actions/workflows/openclaw-performance.yml/runs" \
|
||||
-F event=workflow_dispatch \
|
||||
-F per_page=100 \
|
||||
--jq '.workflow_runs | map(select(.display_title == env.DISPATCH_RUN_NAME)) | sort_by(.created_at) | reverse | .[0].id // empty'
|
||||
)"
|
||||
if [[ -n "$run_id" ]]; then
|
||||
break
|
||||
--jq '[.workflow_runs[] | select(.display_title == env.DISPATCH_RUN_NAME and .head_branch == env.CHILD_WORKFLOW_REF) | .id]'
|
||||
)"; then
|
||||
match_count="$(jq 'length' <<< "$matches_json")"
|
||||
if (( match_count > 1 )); then
|
||||
echo "::error::Multiple runs matched ${dispatch_run_name}; refusing to guess." >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( match_count == 1 )); then
|
||||
run_id="$(jq -r '.[0]' <<< "$matches_json")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
echo "::error::Could not find dispatched run for ${dispatch_run_name}." >&2
|
||||
echo "::error::Could not find exact dispatched run ${dispatch_run_name}; dispatch status ${dispatch_status}. The dispatch was not retried to avoid creating a duplicate child." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$dispatch_status" -ne 0 ]]; then
|
||||
echo "::warning::openclaw-performance.yml dispatch returned status ${dispatch_status}; adopted exact run ${run_id}." >&2
|
||||
fi
|
||||
|
||||
echo "Dispatched openclaw-performance.yml: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
name: NPM Telegram Beta E2E
|
||||
|
||||
run-name: ${{ inputs.dispatch_id != '' && format('NPM Telegram Beta E2E {0}', inputs.dispatch_id) || 'NPM Telegram Beta E2E' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -45,6 +47,11 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
dispatch_id:
|
||||
description: Optional parent workflow dispatch identifier
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
advisory:
|
||||
@@ -86,6 +93,11 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
dispatch_id:
|
||||
description: Optional parent workflow dispatch identifier
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
required: false
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
name: OpenClaw Release Checks
|
||||
|
||||
run-name: ${{ inputs.dispatch_id != '' && format('OpenClaw Release Checks {0}', inputs.dispatch_id) || 'OpenClaw Release Checks' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -88,6 +90,11 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
dispatch_id:
|
||||
description: Optional parent workflow dispatch identifier
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: openclaw-release-checks-${{ inputs.expected_sha || inputs.ref }}-${{ inputs.rerun_group }}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
name: Plugin Prerelease
|
||||
|
||||
run-name: ${{ inputs.dispatch_id != '' && format('Plugin Prerelease {0}', inputs.dispatch_id) || 'Plugin Prerelease' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -18,6 +20,11 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
dispatch_id:
|
||||
description: Optional parent workflow dispatch identifier
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -111,8 +111,14 @@ describe("ci workflow guards", () => {
|
||||
default: false,
|
||||
type: "boolean",
|
||||
});
|
||||
expect(workflow.on.workflow_dispatch.inputs.dispatch_id).toEqual({
|
||||
description: "Optional parent workflow dispatch identifier",
|
||||
required: false,
|
||||
default: "",
|
||||
type: "string",
|
||||
});
|
||||
expect(readFileSync(".github/workflows/ci.yml", "utf8")).toContain(
|
||||
"run-name: ${{ github.event_name == 'workflow_dispatch' && inputs.release_gate && format('CI release gate {0}', inputs.target_ref) || 'CI' }}",
|
||||
"run-name: ${{ github.event_name == 'workflow_dispatch' && inputs.dispatch_id != '' && format('CI {0}', inputs.dispatch_id) || (github.event_name == 'workflow_dispatch' && inputs.release_gate && format('CI release gate {0}', inputs.target_ref) || 'CI') }}",
|
||||
);
|
||||
const preflightSteps = workflow.jobs.preflight.steps;
|
||||
const validationStep = preflightSteps.find(
|
||||
|
||||
@@ -553,18 +553,16 @@ describe("package acceptance workflow", () => {
|
||||
expect(workflow).toContain(
|
||||
'[[ ( "$CHILD_WORKFLOW_REF" == release-ci/* || "$CHILD_WORKFLOW_REF" =~ ^extended-stable/[0-9]{4}\\.([1-9]|1[0-2])\\.33$ ) && -n "${TARGET_SHA// }" && "$head_sha" != "$TARGET_SHA" ]]',
|
||||
);
|
||||
expect(workflow).toContain(
|
||||
'gh_with_retry workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@"',
|
||||
);
|
||||
expect(workflow).toContain('gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1');
|
||||
expect(performanceJob).toContain(
|
||||
'dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"',
|
||||
);
|
||||
expect(performanceJob).toContain('-f dispatch_id="$dispatch_id"');
|
||||
expect(performanceJob).toContain(
|
||||
'DISPATCH_RUN_NAME="$dispatch_run_name" gh_with_retry api -X GET',
|
||||
'DISPATCH_RUN_NAME="$dispatch_run_name" CHILD_WORKFLOW_REF="$CHILD_WORKFLOW_REF"',
|
||||
);
|
||||
expect(performanceJob).toContain(".display_title == env.DISPATCH_RUN_NAME");
|
||||
expect(performanceJob).toContain("Could not find dispatched run for ${dispatch_run_name}.");
|
||||
expect(performanceJob).toContain("Could not find exact dispatched run ${dispatch_run_name}");
|
||||
expect(performanceJob).not.toContain("BEFORE_IDS=");
|
||||
expect(performanceJob).not.toContain(
|
||||
"did not return an Actions run URL; refusing to guess from recent workflow_dispatch runs",
|
||||
@@ -583,6 +581,55 @@ describe("package acceptance workflow", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("adopts exact full-release child runs without retrying ambiguous dispatch posts", () => {
|
||||
const childDispatches = [
|
||||
["normal_ci", "Dispatch and monitor CI"],
|
||||
["plugin_prerelease", "Dispatch and monitor plugin prerelease"],
|
||||
["release_checks", "Dispatch and monitor release checks"],
|
||||
["npm_telegram", "Dispatch and monitor npm Telegram E2E"],
|
||||
["performance", "Dispatch and monitor OpenClaw Performance"],
|
||||
] as const;
|
||||
|
||||
for (const [jobName, stepName] of childDispatches) {
|
||||
const job = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, jobName);
|
||||
const script = workflowStep(job, stepName).run ?? "";
|
||||
|
||||
expect(script.match(/gh workflow run/gu)).toHaveLength(1);
|
||||
expect(script).not.toContain("gh_with_retry workflow run");
|
||||
expectTextToIncludeAll(script, [
|
||||
"A failed dispatch POST can still create a run. Never retry it",
|
||||
"set +e",
|
||||
"dispatch_status=$?",
|
||||
'DISPATCH_RUN_NAME="$dispatch_run_name" CHILD_WORKFLOW_REF="$CHILD_WORKFLOW_REF"',
|
||||
".display_title == env.DISPATCH_RUN_NAME and .head_branch == env.CHILD_WORKFLOW_REF",
|
||||
"Multiple runs matched ${dispatch_run_name}; refusing to guess.",
|
||||
"The dispatch was not retried to avoid creating a duplicate child.",
|
||||
"adopted exact run ${run_id}",
|
||||
]);
|
||||
}
|
||||
|
||||
const workflow = readFileSync(FULL_RELEASE_VALIDATION_WORKFLOW, "utf8");
|
||||
expectTextToIncludeAll(workflow, [
|
||||
'dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-ci"',
|
||||
'dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-plugin-prerelease"',
|
||||
'dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-release-checks"',
|
||||
'dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-npm-telegram"',
|
||||
'args+=(-f dispatch_id="$dispatch_id")',
|
||||
]);
|
||||
expect(readFileSync(".github/workflows/ci.yml", "utf8")).toContain(
|
||||
"format('CI {0}', inputs.dispatch_id)",
|
||||
);
|
||||
expect(readFileSync(".github/workflows/plugin-prerelease.yml", "utf8")).toContain(
|
||||
"format('Plugin Prerelease {0}', inputs.dispatch_id)",
|
||||
);
|
||||
expect(readFileSync(RELEASE_CHECKS_WORKFLOW, "utf8")).toContain(
|
||||
"format('OpenClaw Release Checks {0}', inputs.dispatch_id)",
|
||||
);
|
||||
expect(readFileSync(NPM_TELEGRAM_WORKFLOW, "utf8")).toContain(
|
||||
"format('NPM Telegram Beta E2E {0}', inputs.dispatch_id)",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps exhaustive update migration as a separate manual package gate", () => {
|
||||
const workflow = readFileSync(UPDATE_MIGRATION_WORKFLOW, "utf8");
|
||||
const packageWorkflow = readFileSync(PACKAGE_ACCEPTANCE_WORKFLOW, "utf8");
|
||||
@@ -1668,9 +1715,7 @@ describe("package artifact reuse", () => {
|
||||
const dispatchStep = workflowStep(npmTelegramJob, "Dispatch and monitor npm Telegram E2E");
|
||||
|
||||
expect(workflow).toContain("CHILD_WORKFLOW_REF: ${{ github.ref_name }}");
|
||||
expect(workflow).toContain(
|
||||
'gh_with_retry workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@"',
|
||||
);
|
||||
expect(workflow).toContain('gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1');
|
||||
expect(npmTelegramJob.name).toBe("Run package Telegram E2E");
|
||||
expect(npmTelegramJob.needs).toEqual(["resolve_target"]);
|
||||
expect(npmTelegramJob["timeout-minutes"]).toBe(
|
||||
@@ -1690,9 +1735,10 @@ describe("package artifact reuse", () => {
|
||||
TARGET_SHA: "${{ needs.resolve_target.outputs.sha }}",
|
||||
});
|
||||
expectTextToIncludeAll(dispatchStep.run, [
|
||||
'dispatch_output="$(gh_with_retry workflow run npm-telegram-beta-e2e.yml --ref "$CHILD_WORKFLOW_REF" "${args[@]}")"',
|
||||
"sed -nE 's#.*actions/runs/([0-9]+).*#\\1#p'",
|
||||
"did not return an Actions run URL; refusing to guess from recent workflow_dispatch runs",
|
||||
'dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-npm-telegram"',
|
||||
'dispatch_output="$(gh workflow run npm-telegram-beta-e2e.yml --ref "$CHILD_WORKFLOW_REF" "${args[@]}" 2>&1)"',
|
||||
".display_title == env.DISPATCH_RUN_NAME and .head_branch == env.CHILD_WORKFLOW_REF",
|
||||
"The dispatch was not retried to avoid creating a duplicate child.",
|
||||
'-f harness_ref="$TARGET_SHA"',
|
||||
'args=(-f package_spec="$PACKAGE_SPEC"',
|
||||
'args+=(-f scenario="$SCENARIO")',
|
||||
|
||||
@@ -374,11 +374,11 @@ describe("scripts/lib/plugin-prerelease-test-plan.mjs", () => {
|
||||
workflow.jobs["check-shard"].steps.find((step) => step.name === "Run check shard").run,
|
||||
).toContain("pnpm deadcode:ci");
|
||||
expect(normalCiScript).toContain(
|
||||
'dispatch_and_wait ci.yml -f target_ref="$TARGET_SHA" -f include_android=true',
|
||||
'dispatch_and_wait ci.yml "$dispatch_run_name" -f target_ref="$TARGET_SHA" -f include_android=true -f dispatch_id="$dispatch_id"',
|
||||
);
|
||||
expect(normalCiScript).not.toContain("full_release_validation=true");
|
||||
expect(pluginPrereleaseScript).toContain(
|
||||
'dispatch_and_wait plugin-prerelease.yml -f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true',
|
||||
'dispatch_and_wait plugin-prerelease.yml "$dispatch_run_name" -f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true -f dispatch_id="$dispatch_id"',
|
||||
);
|
||||
expect(pluginManifestScript).toContain("await import(");
|
||||
expect(pluginManifestScript).toContain('"./scripts/lib/plugin-prerelease-test-plan.mjs"');
|
||||
@@ -400,6 +400,12 @@ describe("scripts/lib/plugin-prerelease-test-plan.mjs", () => {
|
||||
required: false,
|
||||
type: "boolean",
|
||||
});
|
||||
expect(pluginWorkflow.on.workflow_dispatch.inputs.dispatch_id).toEqual({
|
||||
description: "Optional parent workflow dispatch identifier",
|
||||
required: false,
|
||||
default: "",
|
||||
type: "string",
|
||||
});
|
||||
expect(pluginManifestEnv).toEqual({
|
||||
EXPECTED_SHA: "${{ inputs.expected_sha }}",
|
||||
FULL_RELEASE_VALIDATION: "${{ inputs.full_release_validation && 'true' || 'false' }}",
|
||||
|
||||
Reference in New Issue
Block a user