diff --git a/.github/workflows/openclaw-performance.yml b/.github/workflows/openclaw-performance.yml index 744ae89e0640..4c2fdaf66c78 100644 --- a/.github/workflows/openclaw-performance.yml +++ b/.github/workflows/openclaw-performance.yml @@ -1045,6 +1045,22 @@ jobs: push_report() { GIT_TERMINAL_PROMPT=0 GIT_CONFIG_COUNT=2 GIT_CONFIG_KEY_0=core.hooksPath GIT_CONFIG_VALUE_0=/dev/null GIT_CONFIG_KEY_1=http.https://github.com/.extraheader GIT_CONFIG_VALUE_1="AUTHORIZATION: basic ${auth_header}" timeout 120s git -C "$reports_root" push origin HEAD:main } + write_publish_success() { + { + echo "### Clawgrit report published" + echo + echo "- Published report: ${REPORT_URL}" + } >> "$GITHUB_STEP_SUMMARY" + } + write_publish_failure() { + { + echo "### Clawgrit report publish failed" + echo + echo "Kova artifacts were uploaded, but publishing the clawgrit report failed after $1 attempts." + echo "Check the ClawSweeper GitHub App installation and contents permission for openclaw/clawgrit-reports." + } >> "$GITHUB_STEP_SUMMARY" + echo "::${annotation}::Kova artifacts uploaded, but clawgrit report publish failed after $1 attempts." + } if [[ "$(git_local config --local --get core.hooksPath)" != "/dev/null" || "$(git_local remote get-url origin)" != "https://github.com/openclaw/clawgrit-reports.git" ]]; then echo "::${annotation}::Prepared reports checkout failed integrity validation." @@ -1055,42 +1071,36 @@ jobs: for attempt in 1 2 3 4 5; do if push_report; then - { - echo "### Clawgrit report published" - echo - echo "- Published report: ${REPORT_URL}" - } >> "$GITHUB_STEP_SUMMARY" + write_publish_success exit 0 fi - if [[ "$attempt" == "5" ]]; then - { - echo "### Clawgrit report publish failed" - echo - echo "Kova artifacts were uploaded, but publishing the clawgrit report failed after ${attempt} attempts." - echo "Check the ClawSweeper GitHub App installation and contents permission for openclaw/clawgrit-reports." - } >> "$GITHUB_STEP_SUMMARY" - echo "::${annotation}::Kova artifacts uploaded, but clawgrit report publish failed after ${attempt} attempts." - exit 1 - fi sleep $((attempt * 2)) if ! timeout 120s git -C "$reports_root" -c core.hooksPath=/dev/null fetch --depth=1 origin main; then - echo "::warning::Unable to refresh openclaw/clawgrit-reports after publish attempt ${attempt}; retrying." - continue - fi - if git_local cat-file -e "FETCH_HEAD:${DEST_REL}/report.json" 2>/dev/null; then - # A timed-out push may have succeeded. Preserve any newer latest pointer. - git_local checkout --detach FETCH_HEAD - continue + if [[ "$attempt" != "5" ]]; then + echo "::warning::Unable to refresh openclaw/clawgrit-reports after publish attempt ${attempt}; retrying." + continue + fi + else + if git_local cat-file -e "FETCH_HEAD:${DEST_REL}/report.json" 2>/dev/null; then + # A timed-out push may have succeeded; verify its unique report before failing. + write_publish_success + exit 0 + fi + if [[ "$attempt" != "5" ]]; then + git_local checkout --detach FETCH_HEAD + # The run path is unique; only the shared latest pointer can conflict. + # Replaying with the current commit wins that pointer deterministically. + if ! git_local cherry-pick -X theirs "$report_commit"; then + git_local cherry-pick --abort || true + echo "::${annotation}::Unable to replay the clawgrit report after a concurrent publish." + exit 1 + fi + report_commit="$(git_local rev-parse HEAD)" + continue + fi fi - git_local checkout --detach FETCH_HEAD - # The run path is unique; only the shared latest pointer can conflict. - # Replaying with the current commit wins that pointer deterministically. - if ! git_local cherry-pick -X theirs "$report_commit"; then - git_local cherry-pick --abort || true - echo "::${annotation}::Unable to replay the clawgrit report after a concurrent publish." - exit 1 - fi - report_commit="$(git_local rev-parse HEAD)" + write_publish_failure "$attempt" + exit 1 done diff --git a/test/scripts/openclaw-performance-workflow.test.ts b/test/scripts/openclaw-performance-workflow.test.ts index 67e3990c83e1..3726328bbd24 100644 --- a/test/scripts/openclaw-performance-workflow.test.ts +++ b/test/scripts/openclaw-performance-workflow.test.ts @@ -320,7 +320,7 @@ printf '%s\\n' \ } }); - it("advertises a clawgrit URL only after an actual successful push", () => { + it("advertises a clawgrit URL only after a direct or remotely verified push", () => { const publish = findStep("Publish to clawgrit reports", "publish"); const root = mkdtempSync(join(realpathSync(tmpdir()), "openclaw-publish-shell-")); const bin = join(root, "bin"); @@ -336,7 +336,8 @@ case "$*" in *"config --local --get core.hooksPath"*) echo /dev/null ;; *"remote get-url origin"*) echo https://github.com/openclaw/clawgrit-reports.git ;; *" push origin HEAD:main"*) printf push > "$STUB_PUSH_MARKER"; exit "\${STUB_PUSH_STATUS:-0}" ;; - *" fetch --depth=1 origin main"*) exit 1 ;; + *" fetch --depth=1 origin main"*) exit "\${STUB_FETCH_STATUS:-1}" ;; + *"cat-file -e FETCH_HEAD:"*) exit "\${STUB_REMOTE_REPORT_STATUS:-1}" ;; *) exit 0 ;; esac `, @@ -347,15 +348,25 @@ esac chmodSync(join(bin, "sleep"), 0o755); chmodSync(join(bin, "timeout"), 0o755); - const execute = (pushStatus: string, appToken: string | null = "test-app-token") => { - const summary = join( - root, - `summary-${pushStatus}-${appToken === null ? "missing" : "token"}.md`, - ); - const pushMarker = join( - root, - `push-${pushStatus}-${appToken === null ? "missing" : "token"}.marker`, - ); + const execute = ({ + pushStatus, + appToken = "test-app-token", + fetchSucceeds = false, + remoteReportPresent = false, + }: { + pushStatus: string; + appToken?: string | null; + fetchSucceeds?: boolean; + remoteReportPresent?: boolean; + }) => { + const scenario = [ + pushStatus, + appToken === null ? "missing" : "token", + fetchSucceeds ? "fetch" : "no-fetch", + remoteReportPresent ? "remote" : "absent", + ].join("-"); + const summary = join(root, `summary-${scenario}.md`); + const pushMarker = join(root, `push-${scenario}.marker`); const result = spawnSync("bash", ["-c", publish.run ?? ""], { encoding: "utf8", env: { @@ -369,8 +380,10 @@ esac REPORT_URL: reportUrl, REPORTS_ROOT: reportsRoot, RUNNER_TEMP: root, + STUB_FETCH_STATUS: fetchSucceeds ? "0" : "1", STUB_PUSH_MARKER: pushMarker, STUB_PUSH_STATUS: pushStatus, + STUB_REMOTE_REPORT_STATUS: remoteReportPresent ? "0" : "1", }, }); return { @@ -381,17 +394,25 @@ esac }; try { - const success = execute("0"); + const success = execute({ pushStatus: "0" }); expect(success.result.status).toBe(0); expect(success.summary).toContain(`- Published report: ${reportUrl}`); - const failure = execute("1"); + const ambiguousSuccess = execute({ + pushStatus: "1", + fetchSucceeds: true, + remoteReportPresent: true, + }); + expect(ambiguousSuccess.result.status).toBe(0); + expect(ambiguousSuccess.summary).toContain(`- Published report: ${reportUrl}`); + + const failure = execute({ pushStatus: "1" }); expect(failure.result.status).toBe(1); expect(failure.summary).toContain("Clawgrit report publish failed"); expect(failure.summary).toContain("ClawSweeper GitHub App installation"); expect(failure.summary).not.toContain("Published report:"); - const missing = execute("0", null); + const missing = execute({ pushStatus: "0", appToken: null }); expect(missing.result.status).toBe(1); expect(missing.result.stdout).toContain("ClawSweeper GitHub App token is unavailable"); expect(missing.summary).toContain("Clawgrit report publish unavailable");