diff --git a/.github/workflows/openclaw-release-telegram-qa.yml b/.github/workflows/openclaw-release-telegram-qa.yml index 428ed0eb9503..1662189bdc0b 100644 --- a/.github/workflows/openclaw-release-telegram-qa.yml +++ b/.github/workflows/openclaw-release-telegram-qa.yml @@ -283,12 +283,6 @@ jobs: "repos/${GITHUB_REPOSITORY}/compare/${candidate_sha}...main" \ --jq '.status' )" - git -C .candidate fetch --force --no-tags origin \ - '+refs/heads/release/*:refs/remotes/origin/release/*' - git -C .candidate fetch --force --no-tags origin \ - '+refs/heads/extended-stable/*:refs/remotes/origin/extended-stable/*' - git -C .candidate fetch --force origin '+refs/tags/v*:refs/tags/v*' - trusted_reason="" trusted_release_branch="" if [[ "$compare_status" == "ahead" || "$compare_status" == "identical" ]]; then @@ -296,31 +290,43 @@ jobs: else normalized_ref="${TARGET_REF#refs/heads/}" if [[ "$normalized_ref" =~ ^(release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*|extended-stable/[0-9]{4}\.[1-9][0-9]*\.33)$ ]]; then - [[ "$(git -C .candidate rev-parse "refs/remotes/origin/${normalized_ref}")" == "$candidate_sha" ]] + branch_sha="$( + git -C .candidate ls-remote --exit-code --refs origin \ + "refs/heads/${normalized_ref}" | + awk 'NR == 1 { print $1 } END { if (NR != 1) exit 1 }' + )" + [[ "$branch_sha" == "$candidate_sha" ]] trusted_reason="release-branch-head" trusted_release_branch="$normalized_ref" elif [[ "$TARGET_REF" =~ ^refs/tags/v ]] || [[ "$TARGET_REF" =~ ^v ]]; then normalized_tag="${TARGET_REF#refs/tags/}" - [[ "$(git -C .candidate rev-parse "refs/tags/${normalized_tag}^{commit}")" == "$candidate_sha" ]] + tag_refs="$( + git -C .candidate ls-remote --exit-code origin \ + "refs/tags/${normalized_tag}" "refs/tags/${normalized_tag}^{}" + )" + awk -v sha="$candidate_sha" '$1 == sha { found = 1 } END { exit(found ? 0 : 1) }' \ + <<<"$tag_refs" trusted_reason="release-tag" elif [[ "$TARGET_REF" =~ ^[a-f0-9]{40}$ && "$TARGET_REF" == "$candidate_sha" ]]; then matching_release_branches="$( - git -C .candidate for-each-ref \ - --format='%(objectname) %(refname:strip=3)' \ - refs/remotes/origin/release \ - refs/remotes/origin/extended-stable | - awk -v sha="$candidate_sha" '$1 == sha { print $2 }' + gh api --paginate \ + "repos/${GITHUB_REPOSITORY}/commits/${candidate_sha}/branches-where-head" \ + --jq '.[].name' | + awk '$0 ~ /^release\/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$/ || + $0 ~ /^extended-stable\/[0-9]{4}\.[1-9][0-9]*\.33$/ { print }' )" if [[ "$(wc -l <<<"$matching_release_branches" | tr -d ' ')" == "1" && -n "$matching_release_branches" ]]; then trusted_reason="release-branch-head" trusted_release_branch="$matching_release_branches" else - while IFS= read -r tag_ref; do - if [[ "$(git -C .candidate rev-parse "${tag_ref}^{commit}")" == "$candidate_sha" ]]; then - trusted_reason="release-tag" - break - fi - done < <(git -C .candidate for-each-ref --format='%(refname)' 'refs/tags/v*') + matching_release_tags="$( + git -C .candidate ls-remote origin 'refs/tags/v*' | + awk -v sha="$candidate_sha" '$1 == sha { sub(/\^\{\}$/, "", $2); print $2 }' | + sort -u + )" + if [[ -n "$matching_release_tags" ]]; then + trusted_reason="release-tag" + fi fi fi fi @@ -974,12 +980,6 @@ jobs: "repos/${GITHUB_REPOSITORY}/compare/${candidate_sha}...main" \ --jq '.status' )" - git fetch --force --no-tags origin \ - '+refs/heads/release/*:refs/remotes/origin/release/*' - git fetch --force --no-tags origin \ - '+refs/heads/extended-stable/*:refs/remotes/origin/extended-stable/*' - git fetch --force origin '+refs/tags/v*:refs/tags/v*' - trusted_reason="" trusted_release_branch="" if [[ "$compare_status" == "ahead" || "$compare_status" == "identical" ]]; then @@ -987,31 +987,42 @@ jobs: else normalized_ref="${TARGET_REF#refs/heads/}" if [[ "$normalized_ref" =~ ^(release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*|extended-stable/[0-9]{4}\.[1-9][0-9]*\.33)$ ]]; then - [[ "$(git rev-parse "refs/remotes/origin/${normalized_ref}")" == "$candidate_sha" ]] + branch_sha="$( + git ls-remote --exit-code --refs origin "refs/heads/${normalized_ref}" | + awk 'NR == 1 { print $1 } END { if (NR != 1) exit 1 }' + )" + [[ "$branch_sha" == "$candidate_sha" ]] trusted_reason="release-branch-head" trusted_release_branch="$normalized_ref" elif [[ "$TARGET_REF" =~ ^refs/tags/v ]] || [[ "$TARGET_REF" =~ ^v ]]; then normalized_tag="${TARGET_REF#refs/tags/}" - [[ "$(git rev-parse "refs/tags/${normalized_tag}^{commit}")" == "$candidate_sha" ]] + tag_refs="$( + git ls-remote --exit-code origin \ + "refs/tags/${normalized_tag}" "refs/tags/${normalized_tag}^{}" + )" + awk -v sha="$candidate_sha" '$1 == sha { found = 1 } END { exit(found ? 0 : 1) }' \ + <<<"$tag_refs" trusted_reason="release-tag" elif [[ "$TARGET_REF" =~ ^[a-f0-9]{40}$ && "$TARGET_REF" == "$candidate_sha" ]]; then matching_release_branches="$( - git for-each-ref \ - --format='%(objectname) %(refname:strip=3)' \ - refs/remotes/origin/release \ - refs/remotes/origin/extended-stable | - awk -v sha="$candidate_sha" '$1 == sha { print $2 }' + gh api --paginate \ + "repos/${GITHUB_REPOSITORY}/commits/${candidate_sha}/branches-where-head" \ + --jq '.[].name' | + awk '$0 ~ /^release\/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$/ || + $0 ~ /^extended-stable\/[0-9]{4}\.[1-9][0-9]*\.33$/ { print }' )" if [[ "$(wc -l <<<"$matching_release_branches" | tr -d ' ')" == "1" && -n "$matching_release_branches" ]]; then trusted_reason="release-branch-head" trusted_release_branch="$matching_release_branches" else - while IFS= read -r tag_ref; do - if [[ "$(git rev-parse "${tag_ref}^{commit}")" == "$candidate_sha" ]]; then - trusted_reason="release-tag" - break - fi - done < <(git for-each-ref --format='%(refname)' 'refs/tags/v*') + matching_release_tags="$( + git ls-remote origin 'refs/tags/v*' | + awk -v sha="$candidate_sha" '$1 == sha { sub(/\^\{\}$/, "", $2); print $2 }' | + sort -u + )" + if [[ -n "$matching_release_tags" ]]; then + trusted_reason="release-tag" + fi fi fi fi diff --git a/AGENTS.md b/AGENTS.md index 22ca608ead42..c7b09e92850a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -134,7 +134,8 @@ Skills own workflows; root owns hard policy and routing. - Crabbox request means real scenario proof: install/update/call/repro user path; not just copy tests and run them remotely. - Visual proof: use Crabbox, set up like a user, then screenshot-verify. No harness/bypass/shortcut unless explicitly asked. - Local agent work is limited to lightweight non-test checks such as `git diff --check`, targeted formatting, and cheap static probes. Tests and computationally intensive work default to the selected remote box. -- In Codex worktrees, direct local `pnpm test*`, `pnpm check*`, `pnpm crabbox:run`, and `scripts/committer` can trigger pnpm dependency reconciliation or install prompts. Prefer `node` wrappers locally and Crabbox/Testbox for pnpm-gated proof. +- In Codex or linked worktrees, direct local `pnpm test*`, `pnpm check*`, `pnpm crabbox:run`, and `scripts/committer` can trigger pnpm dependency reconciliation or install prompts. Prefer `node` wrappers locally and Crabbox/Testbox for pnpm-gated proof. +- Crabbox wrapper `stop` has no `--timing-json`; use `node scripts/crabbox-wrapper.mjs stop --provider --id `. - Repo-native PR worktree may omit `node_modules`; run dependency-backed formatter/docs scripts via Testbox or hosted CI. - Parallel agents share the checkout; never switch its branch while sibling work runs. - Full suites, changed gates, builds, typechecks, lint fan-out, Docker/package/E2E/live/cross-OS proof, or anything computationally intensive: Crabbox/Testbox. diff --git a/test/scripts/openclaw-release-telegram-qa-workflow.test.ts b/test/scripts/openclaw-release-telegram-qa-workflow.test.ts index 9502a94641f3..9defb003f51e 100644 --- a/test/scripts/openclaw-release-telegram-qa-workflow.test.ts +++ b/test/scripts/openclaw-release-telegram-qa-workflow.test.ts @@ -211,9 +211,6 @@ describe("release Telegram QA workflow", () => { const source = readFileSync(WORKFLOW_PATH, "utf8"); expect(source.match(/associatedPullRequests\(first:10\)/gu)).toHaveLength(2); - expect(source.match(/for-each-ref --format='%\(refname\)' 'refs\/tags\/v\*'/gu)).toHaveLength( - 2, - ); expect(source.match(/if \.signature == null then "missing"/gu)).toHaveLength(2); expect(source.match(/\$signature_status" == "invalid"/gu)).toHaveLength(2); expect( @@ -226,13 +223,28 @@ describe("release Telegram QA workflow", () => { 2, ); expect(source.match(/collaborators\/\$\{permission_actor\}\/permission/gu)).toHaveLength(2); - expect( - (source.match(/refs\/remotes\/origin\/extended-stable/gu) ?? []).length, - ).toBeGreaterThanOrEqual(2); expect((source.match(/extended-stable\/\[0-9\]/gu) ?? []).length).toBeGreaterThanOrEqual(2); expect(source).not.toContain("collaborators/${signer}/permission"); }); + it("resolves only candidate-specific provenance refs without fetching histories", () => { + const source = readFileSync(WORKFLOW_PATH, "utf8"); + + expect(source.match(/branches-where-head/gu)).toHaveLength(2); + expect(source.match(/gh api --paginate/gu)).toHaveLength(2); + expect( + source.match(/git(?: -C \.candidate)? ls-remote --exit-code --refs origin/gu), + ).toHaveLength(2); + expect( + source.match(/git(?: -C \.candidate)? ls-remote origin 'refs\/tags\/v\*'/gu), + ).toHaveLength(2); + expect(source).not.toContain("'+refs/heads/release/*:refs/remotes/origin/release/*'"); + expect(source).not.toContain( + "'+refs/heads/extended-stable/*:refs/remotes/origin/extended-stable/*'", + ); + expect(source).not.toContain("'+refs/tags/v*:refs/tags/v*'"); + }); + it("dispatches one accepted trusted-main child from release checks", () => { const releaseSource = readFileSync(RELEASE_CHECKS_PATH, "utf8"); const reusableSource = readFileSync(WORKFLOW_PATH, "utf8");