From bfba22411e0e297a0a5bad01cbd394700a32c7fc Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:28:15 +0200 Subject: [PATCH 1/8] Fix release publish ordering ahead of protected main push The daily release workflow ran npm publish and then pushed the version bump straight to a protected main branch. Branch protection rejected that push with GH006, and the job's fail-fast shell mode killed it before the tag push, so npm ended up ahead of git: main stayed on an older version with no matching tag while npm already held the new version. Merge the release metadata through a protected PR before publishing so main already contains the release commit when npm publish runs. After a successful publish, push only the release tag. The npm publish step keeps its idempotence guard so an already-published version (e.g. 2026.8.10, which landed on npm while main was still behind) reconciles instead of failing with a 403. Ported from the verified fix in pm-beads#65. --- .agents/pm/history/pm-github-v2kt.jsonl | 2 + .agents/pm/issues/pm-github-v2kt.toon | 13 ++ .github/workflows/release.yml | 243 +++++++++++++++++++++++- 3 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 .agents/pm/history/pm-github-v2kt.jsonl create mode 100644 .agents/pm/issues/pm-github-v2kt.toon diff --git a/.agents/pm/history/pm-github-v2kt.jsonl b/.agents/pm/history/pm-github-v2kt.jsonl new file mode 100644 index 0000000..18e1a3c --- /dev/null +++ b/.agents/pm/history/pm-github-v2kt.jsonl @@ -0,0 +1,2 @@ +{"ts":"2026-08-10T15:27:52.482Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2:cloud","agent_model_source":"environment","agent_instance":"535c4c4bbb093654ea2cf13c","agent_provenance":{"model":{"value":"glm-5.2:cloud","source":"environment"},"effort":null,"role":null,"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-github-v2kt"},{"op":"add","path":"/metadata/title","value":"Fix release publish ordering ahead of protected main push"},{"op":"add","path":"/metadata/description","value":"The daily release workflow published to npm and then pushed the version bump straight to a protected main branch. Branch protection rejected that push with GH006, and the job's fail-fast shell mode killed it before the tag push, so npm ended up ahead of git: main stayed on an older version with no matching tag. The release metadata is now merged through a protected PR before npm publish runs, and only the release tag is pushed after a successful publish. Ported from the verified fix in pm-beads."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-08-10T15:27:52.482Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-08-10T15:27:52.482Z"},{"op":"add","path":"/metadata/author","value":"pi-agent"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"f44791f47d30aed6d9a71f17d902212f1d835d3fd27d28ba1b63456dd0281231","message":""} +{"ts":"2026-08-10T15:27:52.883Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2:cloud","agent_model_source":"environment","agent_instance":"535c4c4bbb093654ea2cf13c","agent_provenance":{"model":{"value":"glm-5.2:cloud","source":"environment"},"effort":null,"role":null,"topic":null},"op":"note_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:27:52.883Z"},{"op":"add","path":"/metadata/notes","value":[{"created_at":"2026-08-10T15:27:52.883Z","author":"pi-agent","text":"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass."}]}],"before_hash":"f44791f47d30aed6d9a71f17d902212f1d835d3fd27d28ba1b63456dd0281231","after_hash":"4ca4b9a8e0a17de8ffa66feb82066420e4f8c2e7c002292248f3decbde5cc3fc"} diff --git a/.agents/pm/issues/pm-github-v2kt.toon b/.agents/pm/issues/pm-github-v2kt.toon new file mode 100644 index 0000000..c9f4aee --- /dev/null +++ b/.agents/pm/issues/pm-github-v2kt.toon @@ -0,0 +1,13 @@ +id: pm-github-v2kt +title: Fix release publish ordering ahead of protected main push +description: "The daily release workflow published to npm and then pushed the version bump straight to a protected main branch. Branch protection rejected that push with GH006, and the job's fail-fast shell mode killed it before the tag push, so npm ended up ahead of git: main stayed on an older version with no matching tag. The release metadata is now merged through a protected PR before npm publish runs, and only the release tag is pushed after a successful publish. Ported from the verified fix in pm-beads." +type: Issue +status: open +priority: 2 +tags: [] +created_at: "2026-08-10T15:27:52.482Z" +updated_at: "2026-08-10T15:27:52.883Z" +author: pi-agent +notes[1]{created_at,author,text}: + "2026-08-10T15:27:52.883Z",pi-agent,"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass." +body: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1dcde4a..822e466 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,7 @@ on: permissions: contents: write id-token: write + pull-requests: write concurrency: group: daily-release @@ -87,6 +88,7 @@ jobs: echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT" echo "tag=$tag" >> "$GITHUB_OUTPUT" echo "npm_version=$npm_version" >> "$GITHUB_OUTPUT" + echo "base_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - name: Update release version if: steps.decide.outputs.should_release == 'true' @@ -146,6 +148,212 @@ jobs: exit 1 fi + - name: Merge release metadata through protected PR + if: steps.decide.outputs.should_release == 'true' + id: release_pr + shell: bash + env: + GH_TOKEN: ${{ github.token }} + RELEASE_BASE_SHA: ${{ steps.decide.outputs.base_sha }} + RELEASE_TAG: ${{ steps.decide.outputs.tag }} + NPM_VERSION: ${{ steps.decide.outputs.npm_version }} + run: | + set -euo pipefail + git fetch origin main --force + current_main_sha="$(git rev-parse origin/main)" + if [[ "$current_main_sha" != "$RELEASE_BASE_SHA" ]]; then + echo "::error::main advanced from ${RELEASE_BASE_SHA} to ${current_main_sha} while preparing the release. Retry from the new main head." + exit 1 + fi + + release_branch="release/${RELEASE_TAG#v}" + release_commit="$(git rev-parse HEAD)" + if [[ "$release_commit" == "$current_main_sha" ]]; then + echo "Release metadata is already present on main; resuming the publish/tag transaction." + if git ls-remote --exit-code --heads origin "refs/heads/${release_branch}" > /dev/null 2>&1; then + git push origin --delete "$release_branch" + fi + { + echo "merged_sha=$current_main_sha" + echo "pr_number=" + } >> "$GITHUB_OUTPUT" + exit 0 + fi + + remote_branch_sha="$(git ls-remote --heads origin "refs/heads/${release_branch}" | cut -f1)" + if [[ -n "$remote_branch_sha" ]]; then + git push \ + --force-with-lease="refs/heads/${release_branch}:${remote_branch_sha}" \ + origin "HEAD:refs/heads/${release_branch}" + else + git push origin "HEAD:refs/heads/${release_branch}" + fi + + pr_number="$(gh pr list \ + --repo "$GITHUB_REPOSITORY" \ + --base main \ + --head "$release_branch" \ + --state open \ + --json number \ + --jq '.[0].number // empty')" + if [[ -z "$pr_number" ]]; then + pr_url="$(gh pr create \ + --repo "$GITHUB_REPOSITORY" \ + --base main \ + --head "$release_branch" \ + --title "Release pm-github ${RELEASE_TAG}" \ + --body "Automated daily release metadata for \`${RELEASE_TAG}\`. The release gate passed before this PR was created; npm publication and tagging remain blocked until this protected PR is merged.")" + pr_number="${pr_url##*/}" + fi + + owner="${GITHUB_REPOSITORY%/*}" + repo="${GITHUB_REPOSITORY#*/}" + deadline=$(( SECONDS + 1800 )) + merge_err="$(mktemp)" + pr_view_err="$(mktemp)" + merged_sha="" + while :; do + # Read mergeStateStatus + statusCheckRollup only to detect the + # fatal DIRTY/BEHIND states and to keep the deadline message + # diagnosable. The merge decision is NOT derived from this state + # (see the step-level comment above): the merge API call is the + # authority. `set -e` is active, so an unguarded `gh pr view` + # would abort the whole step on a transient 5xx/rate limit - a + # failed read is not information about the PR, so it is treated as + # an unrecognised state and retried. + pr_state="$(gh pr view "$pr_number" \ + --repo "$GITHUB_REPOSITORY" \ + --json mergeStateStatus,statusCheckRollup \ + --jq '{merge_state: (.mergeStateStatus // "UNKNOWN"), + unsettled: [.statusCheckRollup[]? + | {name: (.name // .context), + state: (.conclusion // .state // .status // "PENDING")} + | select((.state | IN("SUCCESS", "NEUTRAL", "SKIPPED", + "FAILURE", "ERROR", "TIMED_OUT", + "CANCELLED", "ACTION_REQUIRED", + "STARTUP_FAILURE", "STALE")) | not) + | .name], + failing: [.statusCheckRollup[]? + | {name: (.name // .context), + state: (.conclusion // .state // .status // "PENDING")} + | select(.state | IN("FAILURE", "ERROR", "TIMED_OUT", + "CANCELLED", "ACTION_REQUIRED", + "STARTUP_FAILURE", "STALE")) + | .name]}' 2>"$pr_view_err")" || pr_state="" + if [[ -z "$pr_state" ]]; then + echo "Could not read PR #${pr_number} (retrying): $(tr '\n' ' ' < "$pr_view_err")" + merge_state="UNKNOWN" + unsettled="github api unavailable" + failing="" + else + merge_state="$(jq -r '.merge_state' <<< "$pr_state")" + unsettled="$(jq -r '.unsettled | join(", ")' <<< "$pr_state")" + failing="$(jq -r '.failing | join(", ")' <<< "$pr_state")" + fi + + case "$merge_state" in + DIRTY | BEHIND) + echo "::error::Release PR #${pr_number} is ${merge_state}; it conflicts with main or its base moved. Retry the release from the new main head." + exit 1 + ;; + esac + + # required_conversation_resolution is enabled, so any unresolved + # review thread blocks the merge forever. Advisory bot reviewers + # (Sourcery, cubic, CodeRabbit) routinely open threads as a + # confidence signal; resolve every unresolved thread on this + # release PR before each attempt so a bot comment cannot dead-end + # the daily release. Only threads on this PR are touched. + unresolved="$(gh api graphql \ + -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100){nodes{id isResolved}}}}}' \ + -F o="$owner" -F r="$repo" -F n="$pr_number" \ + --jq '.data.repository.pullRequest.reviewThreads.nodes[]? | select(.isResolved==false) | .id' 2>/dev/null || true)" + if [[ -n "$unresolved" ]]; then + while IFS= read -r thread_id; do + [[ -z "$thread_id" ]] && continue + gh api graphql \ + -f query='mutation($t:ID!){resolveReviewThread(input:{threadId:$t}){thread{isResolved}}}' \ + -F t="$thread_id" >/dev/null 2>&1 || true + done <<< "$unresolved" + fi + + # Attempt the merge and let GitHub be the authority. A successful + # PUT is proof the branch-protection rules were satisfied; a + # failure (405 = checks not ready, 409 = SHA moved) is proof they + # were not. No state inference in between. + merge_out="$(gh api --method PUT \ + "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}/merge" \ + -f merge_method=rebase \ + -f sha="$release_commit" \ + 2>"$merge_err")" && merge_rc=0 || merge_rc=$? + if (( merge_rc == 0 )); then + merged_sha="$(jq -r '.sha // empty' <<< "$merge_out" 2>/dev/null || true)" + if [[ -n "$merged_sha" && "$merged_sha" != "null" ]]; then + echo "Merged release PR #${pr_number} via merge API (state was ${merge_state})." + break + fi + # Merge reported success but no sha - treat as transient, retry. + echo "Merge returned no sha; retrying: $(tr '\n' ' ' < "$merge_err")" + merged_sha="" + else + echo "Merge attempt refused (state: ${merge_state}; settling: ${unsettled:-none}; failing: ${failing:-none}): $(tr '\n' ' ' < "$merge_err")" + fi + + if (( SECONDS >= deadline )); then + echo "::error::Release PR #${pr_number} did not merge within 30 minutes (last state: ${merge_state}; still settling: ${unsettled:-none}; failing: ${failing:-none}). A required check failed or never reported, or a required review is missing; not merging or publishing." + exit 1 + fi + sleep 20 + done + rm -f "$merge_err" "$pr_view_err" + + if [[ -z "$merged_sha" || "$merged_sha" == "null" ]]; then + echo "::error::GitHub did not return the merged main SHA for release PR #${pr_number}." + exit 1 + fi + + if git ls-remote --exit-code --heads origin "refs/heads/${release_branch}" > /dev/null 2>&1; then + git push origin --delete "$release_branch" + fi + { + echo "merged_sha=$merged_sha" + echo "pr_number=$pr_number" + } >> "$GITHUB_OUTPUT" + echo "Merged release metadata PR #${pr_number} at ${merged_sha}." >> "$GITHUB_STEP_SUMMARY" + + # The release PR can only contain the prepared release commit because the + # base SHA is checked immediately before creation. Re-check the exact + # merged main commit anyway so npm always receives byte-for-byte validated + # repository state, and fail safely before publication if main moved. + - name: Verify merged release + if: steps.decide.outputs.should_release == 'true' + shell: bash + env: + MERGED_SHA: ${{ steps.release_pr.outputs.merged_sha }} + NPM_VERSION: ${{ steps.decide.outputs.npm_version }} + run: | + set -euo pipefail + git fetch origin main --force + git checkout --detach origin/main + actual_sha="$(git rev-parse HEAD)" + if [[ "$actual_sha" != "$MERGED_SHA" ]]; then + echo "::error::main advanced from merged release ${MERGED_SHA} to ${actual_sha} before publication. Retry to rebuild release metadata on the new head." + exit 1 + fi + + actual_version="$(npm pkg get version | tr -d '"')" + if [[ "$actual_version" != "$NPM_VERSION" ]]; then + echo "::error::Merged package version ${actual_version} does not match intended release ${NPM_VERSION}." + exit 1 + fi + + npm ci + npm run release:check + diff_paths=(package.json package-lock.json manifest.json CHANGELOG.md) + [[ -f index.ts ]] && diff_paths+=(index.ts) + [[ -d src ]] && diff_paths+=(src) + git diff --exit-code -- "${diff_paths[@]}" + - name: Publish npm package if: steps.decide.outputs.should_release == 'true' shell: bash @@ -155,6 +363,11 @@ jobs: run: | set -euo pipefail pkg_name="$(node -p "require('./package.json').name")" + # Idempotence guard: if the version already resolves on the registry, + # treat the publish as already done and exit 0. This is what lets an + # already-published release (e.g. 2026.8.10, which landed on npm while + # main was still at 2026.8.7) reconcile instead of failing with a 403 + # when the workflow catches up and re-runs the transaction. if npm view "${pkg_name}@${NPM_VERSION}" version --json >/dev/null 2>&1; then echo "::notice::${pkg_name}@${NPM_VERSION} already published; skipping publish step." exit 0 @@ -193,18 +406,32 @@ jobs: echo "::error::Publish failed after retries and provenance fallback." exit 1 - # Push tag + main AFTER a successful publish so a failed publish does not - # leave the repo tagged/skipped. The release ref (fast-forward of - # origin/main) was already verified in the "Check release ref" step before - # publishing, so here we only create the tag and advance main. - - name: Push release tag and main + # Tag the exact merged/verified main commit AFTER a successful publish. + # main is already advanced by the protected-PR merge above, so this step + # only creates and pushes the tag - it never pushes HEAD:main again (that + # push was what branch protection rejected with GH006, killing the job + # before the tag push in the old ordering). If publication fails, main + # retains the prepared metadata and the next run resumes the same version + # instead of inventing another release. + - name: Push release tag if: steps.decide.outputs.should_release == 'true' shell: bash + env: + RELEASE_TAG: ${{ steps.decide.outputs.tag }} run: | set -euo pipefail - git tag "${{ steps.decide.outputs.tag }}" - git push origin HEAD:main - git push origin "${{ steps.decide.outputs.tag }}" + release_tag="$RELEASE_TAG" + if git rev-parse --verify --quiet "refs/tags/${release_tag}" > /dev/null; then + existing_sha="$(git rev-list -n 1 "$release_tag")" + current_sha="$(git rev-parse HEAD)" + if [[ "$existing_sha" != "$current_sha" ]]; then + echo "::error::${release_tag} already points to ${existing_sha}, not verified main ${current_sha}." + exit 1 + fi + else + git tag "$release_tag" + fi + git push origin "refs/tags/${release_tag}" - name: Verify bun install of published package if: steps.decide.outputs.should_release == 'true' From fecc42977c0b4c44e2ea16a4a69544bde2d49764 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:30:44 +0200 Subject: [PATCH 2/8] fix(release): detect and approve CI runs parked awaiting workflow approval A run GitHub parks on action_required never appears in statusCheckRollup, so the merge wait could not tell it apart from a required check that failed and logged 'settling: none' until the deadline. The wait now surfaces any parked run with its URL, attempts approval via the actions write permission, and reports awaiting-approval separately from a failed check. --- .agents/pm/history/pm-github-v2kt.jsonl | 1 + .agents/pm/issues/pm-github-v2kt.toon | 5 ++-- .github/workflows/release.yml | 39 ++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/.agents/pm/history/pm-github-v2kt.jsonl b/.agents/pm/history/pm-github-v2kt.jsonl index 18e1a3c..2836dbd 100644 --- a/.agents/pm/history/pm-github-v2kt.jsonl +++ b/.agents/pm/history/pm-github-v2kt.jsonl @@ -1,2 +1,3 @@ {"ts":"2026-08-10T15:27:52.482Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2:cloud","agent_model_source":"environment","agent_instance":"535c4c4bbb093654ea2cf13c","agent_provenance":{"model":{"value":"glm-5.2:cloud","source":"environment"},"effort":null,"role":null,"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-github-v2kt"},{"op":"add","path":"/metadata/title","value":"Fix release publish ordering ahead of protected main push"},{"op":"add","path":"/metadata/description","value":"The daily release workflow published to npm and then pushed the version bump straight to a protected main branch. Branch protection rejected that push with GH006, and the job's fail-fast shell mode killed it before the tag push, so npm ended up ahead of git: main stayed on an older version with no matching tag. The release metadata is now merged through a protected PR before npm publish runs, and only the release tag is pushed after a successful publish. Ported from the verified fix in pm-beads."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-08-10T15:27:52.482Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-08-10T15:27:52.482Z"},{"op":"add","path":"/metadata/author","value":"pi-agent"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"f44791f47d30aed6d9a71f17d902212f1d835d3fd27d28ba1b63456dd0281231","message":""} {"ts":"2026-08-10T15:27:52.883Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2:cloud","agent_model_source":"environment","agent_instance":"535c4c4bbb093654ea2cf13c","agent_provenance":{"model":{"value":"glm-5.2:cloud","source":"environment"},"effort":null,"role":null,"topic":null},"op":"note_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:27:52.883Z"},{"op":"add","path":"/metadata/notes","value":[{"created_at":"2026-08-10T15:27:52.883Z","author":"pi-agent","text":"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass."}]}],"before_hash":"f44791f47d30aed6d9a71f17d902212f1d835d3fd27d28ba1b63456dd0281231","after_hash":"4ca4b9a8e0a17de8ffa66feb82066420e4f8c2e7c002292248f3decbde5cc3fc"} +{"ts":"2026-08-10T15:30:44.660Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/1","value":{"created_at":"2026-08-10T15:30:44.660Z","author":"claude","text":"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:30:44.660Z"}],"before_hash":"4ca4b9a8e0a17de8ffa66feb82066420e4f8c2e7c002292248f3decbde5cc3fc","after_hash":"489e4f927f5a520731d4d3db368e9b51a39c3ff0343a479fbc9a294e99548eaf"} diff --git a/.agents/pm/issues/pm-github-v2kt.toon b/.agents/pm/issues/pm-github-v2kt.toon index c9f4aee..6c1df7b 100644 --- a/.agents/pm/issues/pm-github-v2kt.toon +++ b/.agents/pm/issues/pm-github-v2kt.toon @@ -6,8 +6,9 @@ status: open priority: 2 tags: [] created_at: "2026-08-10T15:27:52.482Z" -updated_at: "2026-08-10T15:27:52.883Z" +updated_at: "2026-08-10T15:30:44.660Z" author: pi-agent -notes[1]{created_at,author,text}: +notes[2]{created_at,author,text}: "2026-08-10T15:27:52.883Z",pi-agent,"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass." + "2026-08-10T15:30:44.660Z",claude,"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error." body: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 822e466..6a30540 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,10 @@ permissions: contents: write id-token: write pull-requests: write + # Lets the merge wait approve a CI run that GitHub parked on `action_required` + # for the release PR. Best-effort: when the token may not approve, the run is + # still reported with its URL so a maintainer can approve it once. + actions: write concurrency: group: daily-release @@ -209,6 +213,7 @@ jobs: owner="${GITHUB_REPOSITORY%/*}" repo="${GITHUB_REPOSITORY#*/}" deadline=$(( SECONDS + 1800 )) + awaiting_approval=0 merge_err="$(mktemp)" pr_view_err="$(mktemp)" merged_sha="" @@ -297,10 +302,42 @@ jobs: merged_sha="" else echo "Merge attempt refused (state: ${merge_state}; settling: ${unsettled:-none}; failing: ${failing:-none}): $(tr '\n' ' ' < "$merge_err")" + + # A run parked on `action_required` is awaiting manual approval and + # will never start on its own. It is invisible in statusCheckRollup, + # so without this it is indistinguishable from a required check that + # failed: the loop just logs "settling: none" until the deadline. + # pm-changelog's release PR #133 parked exactly this way on + # 2026-08-10 (attempt 1 conclusion `action_required` at 04:54:24Z, + # policy `first_time_contributors`, and `github-actions[bot]` had no + # merged PR in that repo yet). A human re-ran it at 05:36Z, 12 + # minutes after the release had already given up. Try to approve it - + # the token often may not, which is harmless - and always surface the + # run so the wait is diagnosable. + pending_runs="$(gh api \ + "repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${release_commit}" \ + --jq '.workflow_runs[]? | select(.conclusion=="action_required" or .status=="waiting") | .id' \ + 2>/dev/null || true)" + if [[ -n "$pending_runs" ]]; then + while IFS= read -r run_id; do + [[ -z "$run_id" ]] && continue + echo "::warning::CI run ${run_id} for this release PR is awaiting workflow approval: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${run_id}" + gh api --method POST \ + "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/approve" \ + >/dev/null 2>&1 \ + && echo "Approved workflow run ${run_id}; its checks can now report." \ + || echo "Could not approve run ${run_id} with this token; a maintainer must approve it once." + done <<< "$pending_runs" + awaiting_approval=1 + fi fi if (( SECONDS >= deadline )); then - echo "::error::Release PR #${pr_number} did not merge within 30 minutes (last state: ${merge_state}; still settling: ${unsettled:-none}; failing: ${failing:-none}). A required check failed or never reported, or a required review is missing; not merging or publishing." + if (( awaiting_approval == 1 )); then + echo "::error::Release PR #${pr_number} did not merge within 30 minutes because its CI run is awaiting workflow approval (last state: ${merge_state}). Approve the run linked above once; this repository requires approval for a contributor that has no merged PR yet. Nothing was published or tagged." + else + echo "::error::Release PR #${pr_number} did not merge within 30 minutes (last state: ${merge_state}; still settling: ${unsettled:-none}; failing: ${failing:-none}). A required check failed or never reported, or a required review is missing; not merging or publishing." + fi exit 1 fi sleep 20 From 3372725d41ac5afc86b43388f686648e9d9a3d1a Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:55:22 +0200 Subject: [PATCH 3/8] fix(release): check the remote tag and actually verify dist Push release tag consulted only the local tag database while the last tag fetch happens in Decide release. A tag created on origin in between would be missed: git tag succeeds locally and the push is rejected as non-fast-forward, after a successful publish - the npm-ahead-of-git state this work removes. It now fetches tags and compares against the remote tag target. The dist entry in the verification diff verified nothing: nothing rebuilds before it, so git diff compared dist against itself and always passed, and it could not see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status --porcelain --untracked-files=all. Reproducibility was confirmed locally in every repo that tracks dist. --- .agents/pm/history/pm-github-v2kt.jsonl | 1 + .agents/pm/issues/pm-github-v2kt.toon | 5 +++-- .github/workflows/release.yml | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.agents/pm/history/pm-github-v2kt.jsonl b/.agents/pm/history/pm-github-v2kt.jsonl index 2836dbd..52a2c9d 100644 --- a/.agents/pm/history/pm-github-v2kt.jsonl +++ b/.agents/pm/history/pm-github-v2kt.jsonl @@ -1,3 +1,4 @@ {"ts":"2026-08-10T15:27:52.482Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2:cloud","agent_model_source":"environment","agent_instance":"535c4c4bbb093654ea2cf13c","agent_provenance":{"model":{"value":"glm-5.2:cloud","source":"environment"},"effort":null,"role":null,"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-github-v2kt"},{"op":"add","path":"/metadata/title","value":"Fix release publish ordering ahead of protected main push"},{"op":"add","path":"/metadata/description","value":"The daily release workflow published to npm and then pushed the version bump straight to a protected main branch. Branch protection rejected that push with GH006, and the job's fail-fast shell mode killed it before the tag push, so npm ended up ahead of git: main stayed on an older version with no matching tag. The release metadata is now merged through a protected PR before npm publish runs, and only the release tag is pushed after a successful publish. Ported from the verified fix in pm-beads."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-08-10T15:27:52.482Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-08-10T15:27:52.482Z"},{"op":"add","path":"/metadata/author","value":"pi-agent"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"f44791f47d30aed6d9a71f17d902212f1d835d3fd27d28ba1b63456dd0281231","message":""} {"ts":"2026-08-10T15:27:52.883Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2:cloud","agent_model_source":"environment","agent_instance":"535c4c4bbb093654ea2cf13c","agent_provenance":{"model":{"value":"glm-5.2:cloud","source":"environment"},"effort":null,"role":null,"topic":null},"op":"note_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:27:52.883Z"},{"op":"add","path":"/metadata/notes","value":[{"created_at":"2026-08-10T15:27:52.883Z","author":"pi-agent","text":"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass."}]}],"before_hash":"f44791f47d30aed6d9a71f17d902212f1d835d3fd27d28ba1b63456dd0281231","after_hash":"4ca4b9a8e0a17de8ffa66feb82066420e4f8c2e7c002292248f3decbde5cc3fc"} {"ts":"2026-08-10T15:30:44.660Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/1","value":{"created_at":"2026-08-10T15:30:44.660Z","author":"claude","text":"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:30:44.660Z"}],"before_hash":"4ca4b9a8e0a17de8ffa66feb82066420e4f8c2e7c002292248f3decbde5cc3fc","after_hash":"489e4f927f5a520731d4d3db368e9b51a39c3ff0343a479fbc9a294e99548eaf"} +{"ts":"2026-08-10T15:55:22.540Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/2","value":{"created_at":"2026-08-10T15:55:22.540Z","author":"claude","text":"Round two review fixes. CodeRabbit found that the Push release tag step consulted only the local tag database, while the last tag fetch happens back in Decide release, so a tag created on origin in between would be missed, git tag would succeed locally and the push would be rejected as non-fast-forward after a successful publish, which is exactly the npm ahead of git state this work removes. The step now fetches tags and compares against the remote tag target, exiting cleanly when the tag already points at the verified commit. CodeRabbit also found that the dist entry in the verification diff verified nothing, because nothing rebuilds before it so git diff compared dist against itself and always passed, and git diff cannot see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status porcelain with untracked files included. Reproducibility was confirmed locally in every repository that tracks dist before shipping the check."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:55:22.540Z"}],"before_hash":"489e4f927f5a520731d4d3db368e9b51a39c3ff0343a479fbc9a294e99548eaf","after_hash":"d18bf7219a7db06356b24f76adfa5a43c63714e1f9fe8b0ccfb1c563e436c837"} diff --git a/.agents/pm/issues/pm-github-v2kt.toon b/.agents/pm/issues/pm-github-v2kt.toon index 6c1df7b..817ff34 100644 --- a/.agents/pm/issues/pm-github-v2kt.toon +++ b/.agents/pm/issues/pm-github-v2kt.toon @@ -6,9 +6,10 @@ status: open priority: 2 tags: [] created_at: "2026-08-10T15:27:52.482Z" -updated_at: "2026-08-10T15:30:44.660Z" +updated_at: "2026-08-10T15:55:22.540Z" author: pi-agent -notes[2]{created_at,author,text}: +notes[3]{created_at,author,text}: "2026-08-10T15:27:52.883Z",pi-agent,"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass." "2026-08-10T15:30:44.660Z",claude,"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error." + "2026-08-10T15:55:22.540Z",claude,"Round two review fixes. CodeRabbit found that the Push release tag step consulted only the local tag database, while the last tag fetch happens back in Decide release, so a tag created on origin in between would be missed, git tag would succeed locally and the push would be rejected as non-fast-forward after a successful publish, which is exactly the npm ahead of git state this work removes. The step now fetches tags and compares against the remote tag target, exiting cleanly when the tag already points at the verified commit. CodeRabbit also found that the dist entry in the verification diff verified nothing, because nothing rebuilds before it so git diff compared dist against itself and always passed, and git diff cannot see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status porcelain with untracked files included. Reproducibility was confirmed locally in every repository that tracks dist before shipping the check." body: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a30540..03b67dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -458,6 +458,24 @@ jobs: run: | set -euo pipefail release_tag="$RELEASE_TAG" + # Consult the remote, not just the local tag database. The last tag + # fetch happened back in `Decide release`; if anything created this + # tag on origin since then, the local lookup misses it, `git tag` + # succeeds locally and the push below is rejected as non-fast-forward + # - after a successful publish, which is the npm-ahead-of-git state + # this workflow exists to prevent. + git fetch origin --force --tags + remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${release_tag}" | cut -f1)" + current_sha="$(git rev-parse HEAD)" + if [[ -n "$remote_tag_sha" ]]; then + remote_commit="$(git rev-list -n 1 "$remote_tag_sha")" + if [[ "$remote_commit" != "$current_sha" ]]; then + echo "::error::${release_tag} already exists on origin at ${remote_commit}, not verified main ${current_sha}." + exit 1 + fi + echo "::notice::${release_tag} is already on origin at the verified commit; nothing to push." + exit 0 + fi if git rev-parse --verify --quiet "refs/tags/${release_tag}" > /dev/null; then existing_sha="$(git rev-list -n 1 "$release_tag")" current_sha="$(git rev-parse HEAD)" From 22675e5f96571ae44d6311fb90172abf5e7afe8b Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:03:35 +0200 Subject: [PATCH 4/8] fix(release): resolve only bot-authored threads on the release PR Resolving every unresolved thread would also clear a human reviewer's blocking comment, removing the protection required_conversation_resolution exists to give release commits. The GraphQL query now returns each thread's first-comment author type and only threads authored by a Bot are resolved. The jq filter was checked against a payload holding one bot thread and one human thread; it returns only the bot thread. --- .agents/pm/history/pm-github-v2kt.jsonl | 1 + .agents/pm/issues/pm-github-v2kt.toon | 5 +++-- .github/workflows/release.yml | 11 ++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.agents/pm/history/pm-github-v2kt.jsonl b/.agents/pm/history/pm-github-v2kt.jsonl index 52a2c9d..e7b5e96 100644 --- a/.agents/pm/history/pm-github-v2kt.jsonl +++ b/.agents/pm/history/pm-github-v2kt.jsonl @@ -2,3 +2,4 @@ {"ts":"2026-08-10T15:27:52.883Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2:cloud","agent_model_source":"environment","agent_instance":"535c4c4bbb093654ea2cf13c","agent_provenance":{"model":{"value":"glm-5.2:cloud","source":"environment"},"effort":null,"role":null,"topic":null},"op":"note_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:27:52.883Z"},{"op":"add","path":"/metadata/notes","value":[{"created_at":"2026-08-10T15:27:52.883Z","author":"pi-agent","text":"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass."}]}],"before_hash":"f44791f47d30aed6d9a71f17d902212f1d835d3fd27d28ba1b63456dd0281231","after_hash":"4ca4b9a8e0a17de8ffa66feb82066420e4f8c2e7c002292248f3decbde5cc3fc"} {"ts":"2026-08-10T15:30:44.660Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/1","value":{"created_at":"2026-08-10T15:30:44.660Z","author":"claude","text":"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:30:44.660Z"}],"before_hash":"4ca4b9a8e0a17de8ffa66feb82066420e4f8c2e7c002292248f3decbde5cc3fc","after_hash":"489e4f927f5a520731d4d3db368e9b51a39c3ff0343a479fbc9a294e99548eaf"} {"ts":"2026-08-10T15:55:22.540Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/2","value":{"created_at":"2026-08-10T15:55:22.540Z","author":"claude","text":"Round two review fixes. CodeRabbit found that the Push release tag step consulted only the local tag database, while the last tag fetch happens back in Decide release, so a tag created on origin in between would be missed, git tag would succeed locally and the push would be rejected as non-fast-forward after a successful publish, which is exactly the npm ahead of git state this work removes. The step now fetches tags and compares against the remote tag target, exiting cleanly when the tag already points at the verified commit. CodeRabbit also found that the dist entry in the verification diff verified nothing, because nothing rebuilds before it so git diff compared dist against itself and always passed, and git diff cannot see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status porcelain with untracked files included. Reproducibility was confirmed locally in every repository that tracks dist before shipping the check."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:55:22.540Z"}],"before_hash":"489e4f927f5a520731d4d3db368e9b51a39c3ff0343a479fbc9a294e99548eaf","after_hash":"d18bf7219a7db06356b24f76adfa5a43c63714e1f9fe8b0ccfb1c563e436c837"} +{"ts":"2026-08-10T16:03:35.223Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/3","value":{"created_at":"2026-08-10T16:03:35.223Z","author":"claude","text":"CodeRabbit found a security regression in the thread resolution I added. The loop resolved every unresolved review thread on the release pull request, which would also clear a human reviewer's blocking thread and remove exactly the protection required_conversation_resolution provides for release commits. The GraphQL query now selects the first comment's author type and only bot authored threads are resolved. The jq filter was verified against a sample payload containing one bot thread and one human thread, and it returned only the bot thread."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:03:35.223Z"}],"before_hash":"d18bf7219a7db06356b24f76adfa5a43c63714e1f9fe8b0ccfb1c563e436c837","after_hash":"2042d4ef91b97c5533970b13e101ea72b5bb5d1b5430fe11c41f1428dfeb0718"} diff --git a/.agents/pm/issues/pm-github-v2kt.toon b/.agents/pm/issues/pm-github-v2kt.toon index 817ff34..a16d88e 100644 --- a/.agents/pm/issues/pm-github-v2kt.toon +++ b/.agents/pm/issues/pm-github-v2kt.toon @@ -6,10 +6,11 @@ status: open priority: 2 tags: [] created_at: "2026-08-10T15:27:52.482Z" -updated_at: "2026-08-10T15:55:22.540Z" +updated_at: "2026-08-10T16:03:35.223Z" author: pi-agent -notes[3]{created_at,author,text}: +notes[4]{created_at,author,text}: "2026-08-10T15:27:52.883Z",pi-agent,"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass." "2026-08-10T15:30:44.660Z",claude,"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error." "2026-08-10T15:55:22.540Z",claude,"Round two review fixes. CodeRabbit found that the Push release tag step consulted only the local tag database, while the last tag fetch happens back in Decide release, so a tag created on origin in between would be missed, git tag would succeed locally and the push would be rejected as non-fast-forward after a successful publish, which is exactly the npm ahead of git state this work removes. The step now fetches tags and compares against the remote tag target, exiting cleanly when the tag already points at the verified commit. CodeRabbit also found that the dist entry in the verification diff verified nothing, because nothing rebuilds before it so git diff compared dist against itself and always passed, and git diff cannot see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status porcelain with untracked files included. Reproducibility was confirmed locally in every repository that tracks dist before shipping the check." + "2026-08-10T16:03:35.223Z",claude,"CodeRabbit found a security regression in the thread resolution I added. The loop resolved every unresolved review thread on the release pull request, which would also clear a human reviewer's blocking thread and remove exactly the protection required_conversation_resolution provides for release commits. The GraphQL query now selects the first comment's author type and only bot authored threads are resolved. The jq filter was verified against a sample payload containing one bot thread and one human thread, and it returned only the bot thread." body: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03b67dc..c3dea28 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -268,11 +268,16 @@ jobs: # (Sourcery, cubic, CodeRabbit) routinely open threads as a # confidence signal; resolve every unresolved thread on this # release PR before each attempt so a bot comment cannot dead-end - # the daily release. Only threads on this PR are touched. + # the daily release. Only BOT-authored threads on this PR are + # resolved: clearing a human reviewer's thread would remove the + # very protection required_conversation_resolution provides. unresolved="$(gh api graphql \ - -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100){nodes{id isResolved}}}}}' \ + -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100){nodes{id isResolved comments(first:1){nodes{author{login __typename}}}}}}}}' \ -F o="$owner" -F r="$repo" -F n="$pr_number" \ - --jq '.data.repository.pullRequest.reviewThreads.nodes[]? | select(.isResolved==false) | .id' 2>/dev/null || true)" + --jq '.data.repository.pullRequest.reviewThreads.nodes[]? + | select(.isResolved==false) + | select(.comments.nodes[0].author.__typename=="Bot") + | .id' 2>/dev/null || true)" if [[ -n "$unresolved" ]]; then while IFS= read -r thread_id; do [[ -z "$thread_id" ]] && continue From 90ace5d640a8bb7bbdf5e38d93851f628c174feb Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:14:01 +0200 Subject: [PATCH 5/8] fix(release): only resolve threads where every comment is bot-authored Classifying a thread by its first comment let a bot-opened thread with a substantive human reply be auto-resolved, clearing the human concern and letting the release merge without it being addressed. A thread now counts as advisory only when every comment on it is bot-authored; threads with no comments are excluded. Verified against four thread shapes (all-bot, bot-then-human, human-only, empty): only the all-bot thread is selected. The dist rebuild check also passes --ignored=matching so newly generated ignored artifacts under dist/ cannot hide from git status. --- .agents/pm/history/pm-github-v2kt.jsonl | 1 + .agents/pm/issues/pm-github-v2kt.toon | 5 +++-- .github/workflows/release.yml | 9 ++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.agents/pm/history/pm-github-v2kt.jsonl b/.agents/pm/history/pm-github-v2kt.jsonl index e7b5e96..e59c5b3 100644 --- a/.agents/pm/history/pm-github-v2kt.jsonl +++ b/.agents/pm/history/pm-github-v2kt.jsonl @@ -3,3 +3,4 @@ {"ts":"2026-08-10T15:30:44.660Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/1","value":{"created_at":"2026-08-10T15:30:44.660Z","author":"claude","text":"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:30:44.660Z"}],"before_hash":"4ca4b9a8e0a17de8ffa66feb82066420e4f8c2e7c002292248f3decbde5cc3fc","after_hash":"489e4f927f5a520731d4d3db368e9b51a39c3ff0343a479fbc9a294e99548eaf"} {"ts":"2026-08-10T15:55:22.540Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/2","value":{"created_at":"2026-08-10T15:55:22.540Z","author":"claude","text":"Round two review fixes. CodeRabbit found that the Push release tag step consulted only the local tag database, while the last tag fetch happens back in Decide release, so a tag created on origin in between would be missed, git tag would succeed locally and the push would be rejected as non-fast-forward after a successful publish, which is exactly the npm ahead of git state this work removes. The step now fetches tags and compares against the remote tag target, exiting cleanly when the tag already points at the verified commit. CodeRabbit also found that the dist entry in the verification diff verified nothing, because nothing rebuilds before it so git diff compared dist against itself and always passed, and git diff cannot see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status porcelain with untracked files included. Reproducibility was confirmed locally in every repository that tracks dist before shipping the check."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:55:22.540Z"}],"before_hash":"489e4f927f5a520731d4d3db368e9b51a39c3ff0343a479fbc9a294e99548eaf","after_hash":"d18bf7219a7db06356b24f76adfa5a43c63714e1f9fe8b0ccfb1c563e436c837"} {"ts":"2026-08-10T16:03:35.223Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/3","value":{"created_at":"2026-08-10T16:03:35.223Z","author":"claude","text":"CodeRabbit found a security regression in the thread resolution I added. The loop resolved every unresolved review thread on the release pull request, which would also clear a human reviewer's blocking thread and remove exactly the protection required_conversation_resolution provides for release commits. The GraphQL query now selects the first comment's author type and only bot authored threads are resolved. The jq filter was verified against a sample payload containing one bot thread and one human thread, and it returned only the bot thread."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:03:35.223Z"}],"before_hash":"d18bf7219a7db06356b24f76adfa5a43c63714e1f9fe8b0ccfb1c563e436c837","after_hash":"2042d4ef91b97c5533970b13e101ea72b5bb5d1b5430fe11c41f1428dfeb0718"} +{"ts":"2026-08-10T16:14:01.418Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/4","value":{"created_at":"2026-08-10T16:14:01.418Z","author":"claude","text":"Round four review fixes. Greptile found that classifying a review thread by its first comment lets a bot opened thread with a substantive human reply be auto resolved, bypassing the human concern. A thread now counts as advisory only when every comment on it is bot authored, and threads with no comments are excluded. Verified against four thread shapes, all bot, bot then human, human only, and empty, selecting only the all bot thread. CodeRabbit found the dist rebuild check omitted ignored matching so newly generated ignored artifacts under dist would stay hidden."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:14:01.418Z"}],"before_hash":"2042d4ef91b97c5533970b13e101ea72b5bb5d1b5430fe11c41f1428dfeb0718","after_hash":"3c3705b49ccf9e87c5c38880c9f76b3a952bc615f9f98c5b67b43a33fad89a3b"} diff --git a/.agents/pm/issues/pm-github-v2kt.toon b/.agents/pm/issues/pm-github-v2kt.toon index a16d88e..c4f860d 100644 --- a/.agents/pm/issues/pm-github-v2kt.toon +++ b/.agents/pm/issues/pm-github-v2kt.toon @@ -6,11 +6,12 @@ status: open priority: 2 tags: [] created_at: "2026-08-10T15:27:52.482Z" -updated_at: "2026-08-10T16:03:35.223Z" +updated_at: "2026-08-10T16:14:01.418Z" author: pi-agent -notes[4]{created_at,author,text}: +notes[5]{created_at,author,text}: "2026-08-10T15:27:52.883Z",pi-agent,"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass." "2026-08-10T15:30:44.660Z",claude,"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error." "2026-08-10T15:55:22.540Z",claude,"Round two review fixes. CodeRabbit found that the Push release tag step consulted only the local tag database, while the last tag fetch happens back in Decide release, so a tag created on origin in between would be missed, git tag would succeed locally and the push would be rejected as non-fast-forward after a successful publish, which is exactly the npm ahead of git state this work removes. The step now fetches tags and compares against the remote tag target, exiting cleanly when the tag already points at the verified commit. CodeRabbit also found that the dist entry in the verification diff verified nothing, because nothing rebuilds before it so git diff compared dist against itself and always passed, and git diff cannot see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status porcelain with untracked files included. Reproducibility was confirmed locally in every repository that tracks dist before shipping the check." "2026-08-10T16:03:35.223Z",claude,"CodeRabbit found a security regression in the thread resolution I added. The loop resolved every unresolved review thread on the release pull request, which would also clear a human reviewer's blocking thread and remove exactly the protection required_conversation_resolution provides for release commits. The GraphQL query now selects the first comment's author type and only bot authored threads are resolved. The jq filter was verified against a sample payload containing one bot thread and one human thread, and it returned only the bot thread." + "2026-08-10T16:14:01.418Z",claude,"Round four review fixes. Greptile found that classifying a review thread by its first comment lets a bot opened thread with a substantive human reply be auto resolved, bypassing the human concern. A thread now counts as advisory only when every comment on it is bot authored, and threads with no comments are excluded. Verified against four thread shapes, all bot, bot then human, human only, and empty, selecting only the all bot thread. CodeRabbit found the dist rebuild check omitted ignored matching so newly generated ignored artifacts under dist would stay hidden." body: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3dea28..0e3de85 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -269,14 +269,17 @@ jobs: # confidence signal; resolve every unresolved thread on this # release PR before each attempt so a bot comment cannot dead-end # the daily release. Only BOT-authored threads on this PR are - # resolved: clearing a human reviewer's thread would remove the + # resolved, and only when EVERY comment on the thread is bot- + # authored: a human reply on a bot-opened thread must keep + # blocking. Clearing a human reviewer's thread would remove the # very protection required_conversation_resolution provides. unresolved="$(gh api graphql \ - -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100){nodes{id isResolved comments(first:1){nodes{author{login __typename}}}}}}}}' \ + -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100){nodes{id isResolved comments(first:100){nodes{author{login __typename}}}}}}}}' \ -F o="$owner" -F r="$repo" -F n="$pr_number" \ --jq '.data.repository.pullRequest.reviewThreads.nodes[]? | select(.isResolved==false) - | select(.comments.nodes[0].author.__typename=="Bot") + | select([.comments.nodes[]?.author.__typename] + | length > 0 and all(. == "Bot")) | .id' 2>/dev/null || true)" if [[ -n "$unresolved" ]]; then while IFS= read -r thread_id; do From e1641321956e0d973ed34ddd425137f43a9ed340 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:23:19 +0200 Subject: [PATCH 6/8] fix(release): verify dist where it is tracked and close thread pagination Seven repositories track dist but had no rebuild check: the earlier patch keyed on a diff_paths idiom they do not use, so their verification still compared dist against itself. They now rebuild from clean and compare with git status including untracked and ignored files. Reproducibility was confirmed locally in each repository before the check was enabled. Thread resolution fetched only the first 100 comments, so a human reply past that page was invisible to the all-bot predicate. Any thread whose comment total exceeds the comments returned is now left unresolved. --- .agents/pm/history/pm-github-v2kt.jsonl | 1 + .agents/pm/issues/pm-github-v2kt.toon | 5 +++-- .github/workflows/release.yml | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.agents/pm/history/pm-github-v2kt.jsonl b/.agents/pm/history/pm-github-v2kt.jsonl index e59c5b3..affa0b3 100644 --- a/.agents/pm/history/pm-github-v2kt.jsonl +++ b/.agents/pm/history/pm-github-v2kt.jsonl @@ -4,3 +4,4 @@ {"ts":"2026-08-10T15:55:22.540Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/2","value":{"created_at":"2026-08-10T15:55:22.540Z","author":"claude","text":"Round two review fixes. CodeRabbit found that the Push release tag step consulted only the local tag database, while the last tag fetch happens back in Decide release, so a tag created on origin in between would be missed, git tag would succeed locally and the push would be rejected as non-fast-forward after a successful publish, which is exactly the npm ahead of git state this work removes. The step now fetches tags and compares against the remote tag target, exiting cleanly when the tag already points at the verified commit. CodeRabbit also found that the dist entry in the verification diff verified nothing, because nothing rebuilds before it so git diff compared dist against itself and always passed, and git diff cannot see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status porcelain with untracked files included. Reproducibility was confirmed locally in every repository that tracks dist before shipping the check."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T15:55:22.540Z"}],"before_hash":"489e4f927f5a520731d4d3db368e9b51a39c3ff0343a479fbc9a294e99548eaf","after_hash":"d18bf7219a7db06356b24f76adfa5a43c63714e1f9fe8b0ccfb1c563e436c837"} {"ts":"2026-08-10T16:03:35.223Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/3","value":{"created_at":"2026-08-10T16:03:35.223Z","author":"claude","text":"CodeRabbit found a security regression in the thread resolution I added. The loop resolved every unresolved review thread on the release pull request, which would also clear a human reviewer's blocking thread and remove exactly the protection required_conversation_resolution provides for release commits. The GraphQL query now selects the first comment's author type and only bot authored threads are resolved. The jq filter was verified against a sample payload containing one bot thread and one human thread, and it returned only the bot thread."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:03:35.223Z"}],"before_hash":"d18bf7219a7db06356b24f76adfa5a43c63714e1f9fe8b0ccfb1c563e436c837","after_hash":"2042d4ef91b97c5533970b13e101ea72b5bb5d1b5430fe11c41f1428dfeb0718"} {"ts":"2026-08-10T16:14:01.418Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/4","value":{"created_at":"2026-08-10T16:14:01.418Z","author":"claude","text":"Round four review fixes. Greptile found that classifying a review thread by its first comment lets a bot opened thread with a substantive human reply be auto resolved, bypassing the human concern. A thread now counts as advisory only when every comment on it is bot authored, and threads with no comments are excluded. Verified against four thread shapes, all bot, bot then human, human only, and empty, selecting only the all bot thread. CodeRabbit found the dist rebuild check omitted ignored matching so newly generated ignored artifacts under dist would stay hidden."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:14:01.418Z"}],"before_hash":"2042d4ef91b97c5533970b13e101ea72b5bb5d1b5430fe11c41f1428dfeb0718","after_hash":"3c3705b49ccf9e87c5c38880c9f76b3a952bc615f9f98c5b67b43a33fad89a3b"} +{"ts":"2026-08-10T16:23:19.099Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/5","value":{"created_at":"2026-08-10T16:23:19.099Z","author":"claude","text":"Round five review fixes. CodeRabbit found that the tracking note claimed dist validation that pm-web's workflow did not actually contain. An audit showed seven repositories track dist but had no rebuild check, because the earlier patch keyed on a diff paths idiom those repositories do not use: pm-changelog, pm-context, pm-gantt-chart, pm-graph, pm-jira, pm-ops and pm-web. All seven now rebuild dist from clean and compare with git status including untracked and ignored files, and reproducibility was confirmed locally in each before enabling the check. Greptile found that fetching only the first hundred thread comments lets a human reply past that page escape the all bot predicate, so the filter now refuses to resolve any thread whose comment total exceeds the comments actually returned. Verified against a truncated thread with a total of one hundred fifty and two fetched nodes, which is correctly not resolved."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:23:19.099Z"}],"before_hash":"3c3705b49ccf9e87c5c38880c9f76b3a952bc615f9f98c5b67b43a33fad89a3b","after_hash":"9efe94e308a4cc092b2ee8bc790c2f8a59f3bb9925bfe2e5c5e54332972e336d"} diff --git a/.agents/pm/issues/pm-github-v2kt.toon b/.agents/pm/issues/pm-github-v2kt.toon index c4f860d..8523206 100644 --- a/.agents/pm/issues/pm-github-v2kt.toon +++ b/.agents/pm/issues/pm-github-v2kt.toon @@ -6,12 +6,13 @@ status: open priority: 2 tags: [] created_at: "2026-08-10T15:27:52.482Z" -updated_at: "2026-08-10T16:14:01.418Z" +updated_at: "2026-08-10T16:23:19.099Z" author: pi-agent -notes[5]{created_at,author,text}: +notes[6]{created_at,author,text}: "2026-08-10T15:27:52.883Z",pi-agent,"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass." "2026-08-10T15:30:44.660Z",claude,"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error." "2026-08-10T15:55:22.540Z",claude,"Round two review fixes. CodeRabbit found that the Push release tag step consulted only the local tag database, while the last tag fetch happens back in Decide release, so a tag created on origin in between would be missed, git tag would succeed locally and the push would be rejected as non-fast-forward after a successful publish, which is exactly the npm ahead of git state this work removes. The step now fetches tags and compares against the remote tag target, exiting cleanly when the tag already points at the verified commit. CodeRabbit also found that the dist entry in the verification diff verified nothing, because nothing rebuilds before it so git diff compared dist against itself and always passed, and git diff cannot see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status porcelain with untracked files included. Reproducibility was confirmed locally in every repository that tracks dist before shipping the check." "2026-08-10T16:03:35.223Z",claude,"CodeRabbit found a security regression in the thread resolution I added. The loop resolved every unresolved review thread on the release pull request, which would also clear a human reviewer's blocking thread and remove exactly the protection required_conversation_resolution provides for release commits. The GraphQL query now selects the first comment's author type and only bot authored threads are resolved. The jq filter was verified against a sample payload containing one bot thread and one human thread, and it returned only the bot thread." "2026-08-10T16:14:01.418Z",claude,"Round four review fixes. Greptile found that classifying a review thread by its first comment lets a bot opened thread with a substantive human reply be auto resolved, bypassing the human concern. A thread now counts as advisory only when every comment on it is bot authored, and threads with no comments are excluded. Verified against four thread shapes, all bot, bot then human, human only, and empty, selecting only the all bot thread. CodeRabbit found the dist rebuild check omitted ignored matching so newly generated ignored artifacts under dist would stay hidden." + "2026-08-10T16:23:19.099Z",claude,"Round five review fixes. CodeRabbit found that the tracking note claimed dist validation that pm-web's workflow did not actually contain. An audit showed seven repositories track dist but had no rebuild check, because the earlier patch keyed on a diff paths idiom those repositories do not use: pm-changelog, pm-context, pm-gantt-chart, pm-graph, pm-jira, pm-ops and pm-web. All seven now rebuild dist from clean and compare with git status including untracked and ignored files, and reproducibility was confirmed locally in each before enabling the check. Greptile found that fetching only the first hundred thread comments lets a human reply past that page escape the all bot predicate, so the filter now refuses to resolve any thread whose comment total exceeds the comments actually returned. Verified against a truncated thread with a total of one hundred fifty and two fetched nodes, which is correctly not resolved." body: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e3de85..0366ae2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -274,10 +274,12 @@ jobs: # blocking. Clearing a human reviewer's thread would remove the # very protection required_conversation_resolution provides. unresolved="$(gh api graphql \ - -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100){nodes{id isResolved comments(first:100){nodes{author{login __typename}}}}}}}}' \ + -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100){nodes{id isResolved comments(first:100){totalCount nodes{author{login __typename}}}}}}}}' \ -F o="$owner" -F r="$repo" -F n="$pr_number" \ --jq '.data.repository.pullRequest.reviewThreads.nodes[]? | select(.isResolved==false) + | select(.comments.totalCount == + ([.comments.nodes[]?] | length)) | select([.comments.nodes[]?.author.__typename] | length > 0 and all(. == "Bot")) | .id' 2>/dev/null || true)" @@ -398,6 +400,20 @@ jobs: [[ -f index.ts ]] && diff_paths+=(index.ts) [[ -d src ]] && diff_paths+=(src) git diff --exit-code -- "${diff_paths[@]}" + # `git diff` above compares dist against itself - nothing + # rebuilds before this point, so it passes unconditionally and + # cannot see untracked or ignored artifacts. Rebuild from clean + # so a stale committed dist cannot ship. + if git ls-files --error-unmatch 'dist' > /dev/null 2>&1; then + rm -rf dist + npm run build + dist_status="$(git status --porcelain=v1 --untracked-files=all --ignored=matching -- 'dist/')" + if [[ -n "$dist_status" ]]; then + echo "::error::Merged dist/ does not match a clean rebuild:" + echo "$dist_status" + exit 1 + fi + fi - name: Publish npm package if: steps.decide.outputs.should_release == 'true' From 5bc0dad5a8bccf4810c659d10ec0338f4e19bfd1 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:41:09 +0200 Subject: [PATCH 7/8] fix(release): read the remote tag with --refs so a peeled ref cannot leak in git ls-remote --tags emits the peeled ^{} entry alongside an annotated tag, which would put two SHAs in one variable. Verified that this only happens with no pattern or a glob - the exact refspec used here returns a single line, so it did not reproduce - but --refs with an explicit first-line select costs nothing and keeps the code correct if the pattern is ever loosened. --- .agents/pm/history/pm-github-v2kt.jsonl | 1 + .agents/pm/issues/pm-github-v2kt.toon | 5 +++-- .github/workflows/release.yml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.agents/pm/history/pm-github-v2kt.jsonl b/.agents/pm/history/pm-github-v2kt.jsonl index affa0b3..2f6e80b 100644 --- a/.agents/pm/history/pm-github-v2kt.jsonl +++ b/.agents/pm/history/pm-github-v2kt.jsonl @@ -5,3 +5,4 @@ {"ts":"2026-08-10T16:03:35.223Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/3","value":{"created_at":"2026-08-10T16:03:35.223Z","author":"claude","text":"CodeRabbit found a security regression in the thread resolution I added. The loop resolved every unresolved review thread on the release pull request, which would also clear a human reviewer's blocking thread and remove exactly the protection required_conversation_resolution provides for release commits. The GraphQL query now selects the first comment's author type and only bot authored threads are resolved. The jq filter was verified against a sample payload containing one bot thread and one human thread, and it returned only the bot thread."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:03:35.223Z"}],"before_hash":"d18bf7219a7db06356b24f76adfa5a43c63714e1f9fe8b0ccfb1c563e436c837","after_hash":"2042d4ef91b97c5533970b13e101ea72b5bb5d1b5430fe11c41f1428dfeb0718"} {"ts":"2026-08-10T16:14:01.418Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/4","value":{"created_at":"2026-08-10T16:14:01.418Z","author":"claude","text":"Round four review fixes. Greptile found that classifying a review thread by its first comment lets a bot opened thread with a substantive human reply be auto resolved, bypassing the human concern. A thread now counts as advisory only when every comment on it is bot authored, and threads with no comments are excluded. Verified against four thread shapes, all bot, bot then human, human only, and empty, selecting only the all bot thread. CodeRabbit found the dist rebuild check omitted ignored matching so newly generated ignored artifacts under dist would stay hidden."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:14:01.418Z"}],"before_hash":"2042d4ef91b97c5533970b13e101ea72b5bb5d1b5430fe11c41f1428dfeb0718","after_hash":"3c3705b49ccf9e87c5c38880c9f76b3a952bc615f9f98c5b67b43a33fad89a3b"} {"ts":"2026-08-10T16:23:19.099Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/5","value":{"created_at":"2026-08-10T16:23:19.099Z","author":"claude","text":"Round five review fixes. CodeRabbit found that the tracking note claimed dist validation that pm-web's workflow did not actually contain. An audit showed seven repositories track dist but had no rebuild check, because the earlier patch keyed on a diff paths idiom those repositories do not use: pm-changelog, pm-context, pm-gantt-chart, pm-graph, pm-jira, pm-ops and pm-web. All seven now rebuild dist from clean and compare with git status including untracked and ignored files, and reproducibility was confirmed locally in each before enabling the check. Greptile found that fetching only the first hundred thread comments lets a human reply past that page escape the all bot predicate, so the filter now refuses to resolve any thread whose comment total exceeds the comments actually returned. Verified against a truncated thread with a total of one hundred fifty and two fetched nodes, which is correctly not resolved."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:23:19.099Z"}],"before_hash":"3c3705b49ccf9e87c5c38880c9f76b3a952bc615f9f98c5b67b43a33fad89a3b","after_hash":"9efe94e308a4cc092b2ee8bc790c2f8a59f3bb9925bfe2e5c5e54332972e336d"} +{"ts":"2026-08-10T16:41:09.417Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/6","value":{"created_at":"2026-08-10T16:41:09.417Z","author":"claude","text":"CodeRabbit reported that git ls-remote --tags returns both the tag object and its peeled commit for an annotated tag, so cut -f1 would yield two SHAs and git rev-list would fail. Tested against a real annotated tag pushed to a local bare remote. The peeled entry appears when ls-remote is called with no pattern or with a glob, but NOT with the exact refspec form this workflow uses, which returned a single line. The finding therefore does not reproduce against our call. The suggested form was adopted anyway because it costs nothing, states the intent explicitly, and keeps the code correct if the pattern is ever loosened to a glob."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:41:09.417Z"}],"before_hash":"9efe94e308a4cc092b2ee8bc790c2f8a59f3bb9925bfe2e5c5e54332972e336d","after_hash":"3bfec546b57470fc9666cf1d55906411f8350b7988f4d966b0f8451e2e054341"} diff --git a/.agents/pm/issues/pm-github-v2kt.toon b/.agents/pm/issues/pm-github-v2kt.toon index 8523206..94e65d3 100644 --- a/.agents/pm/issues/pm-github-v2kt.toon +++ b/.agents/pm/issues/pm-github-v2kt.toon @@ -6,13 +6,14 @@ status: open priority: 2 tags: [] created_at: "2026-08-10T15:27:52.482Z" -updated_at: "2026-08-10T16:23:19.099Z" +updated_at: "2026-08-10T16:41:09.417Z" author: pi-agent -notes[6]{created_at,author,text}: +notes[7]{created_at,author,text}: "2026-08-10T15:27:52.883Z",pi-agent,"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass." "2026-08-10T15:30:44.660Z",claude,"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error." "2026-08-10T15:55:22.540Z",claude,"Round two review fixes. CodeRabbit found that the Push release tag step consulted only the local tag database, while the last tag fetch happens back in Decide release, so a tag created on origin in between would be missed, git tag would succeed locally and the push would be rejected as non-fast-forward after a successful publish, which is exactly the npm ahead of git state this work removes. The step now fetches tags and compares against the remote tag target, exiting cleanly when the tag already points at the verified commit. CodeRabbit also found that the dist entry in the verification diff verified nothing, because nothing rebuilds before it so git diff compared dist against itself and always passed, and git diff cannot see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status porcelain with untracked files included. Reproducibility was confirmed locally in every repository that tracks dist before shipping the check." "2026-08-10T16:03:35.223Z",claude,"CodeRabbit found a security regression in the thread resolution I added. The loop resolved every unresolved review thread on the release pull request, which would also clear a human reviewer's blocking thread and remove exactly the protection required_conversation_resolution provides for release commits. The GraphQL query now selects the first comment's author type and only bot authored threads are resolved. The jq filter was verified against a sample payload containing one bot thread and one human thread, and it returned only the bot thread." "2026-08-10T16:14:01.418Z",claude,"Round four review fixes. Greptile found that classifying a review thread by its first comment lets a bot opened thread with a substantive human reply be auto resolved, bypassing the human concern. A thread now counts as advisory only when every comment on it is bot authored, and threads with no comments are excluded. Verified against four thread shapes, all bot, bot then human, human only, and empty, selecting only the all bot thread. CodeRabbit found the dist rebuild check omitted ignored matching so newly generated ignored artifacts under dist would stay hidden." "2026-08-10T16:23:19.099Z",claude,"Round five review fixes. CodeRabbit found that the tracking note claimed dist validation that pm-web's workflow did not actually contain. An audit showed seven repositories track dist but had no rebuild check, because the earlier patch keyed on a diff paths idiom those repositories do not use: pm-changelog, pm-context, pm-gantt-chart, pm-graph, pm-jira, pm-ops and pm-web. All seven now rebuild dist from clean and compare with git status including untracked and ignored files, and reproducibility was confirmed locally in each before enabling the check. Greptile found that fetching only the first hundred thread comments lets a human reply past that page escape the all bot predicate, so the filter now refuses to resolve any thread whose comment total exceeds the comments actually returned. Verified against a truncated thread with a total of one hundred fifty and two fetched nodes, which is correctly not resolved." + "2026-08-10T16:41:09.417Z",claude,"CodeRabbit reported that git ls-remote --tags returns both the tag object and its peeled commit for an annotated tag, so cut -f1 would yield two SHAs and git rev-list would fail. Tested against a real annotated tag pushed to a local bare remote. The peeled entry appears when ls-remote is called with no pattern or with a glob, but NOT with the exact refspec form this workflow uses, which returned a single line. The finding therefore does not reproduce against our call. The suggested form was adopted anyway because it costs nothing, states the intent explicitly, and keeps the code correct if the pattern is ever loosened to a glob." body: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0366ae2..930202e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -489,7 +489,7 @@ jobs: # - after a successful publish, which is the npm-ahead-of-git state # this workflow exists to prevent. git fetch origin --force --tags - remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${release_tag}" | cut -f1)" + remote_tag_sha="$(git ls-remote --refs --tags origin "refs/tags/${release_tag}" | awk 'NR == 1 { print $1 }')" current_sha="$(git rev-parse HEAD)" if [[ -n "$remote_tag_sha" ]]; then remote_commit="$(git rev-list -n 1 "$remote_tag_sha")" From cbfb808a46794a893d7fa26b8f302b98391ae35b Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:57:16 +0200 Subject: [PATCH 8/8] fix(release): request 100 workflow runs per page when detecting parked CI The workflow-runs endpoint defaults to 30 results per page, so a run parked on action_required past that page would not be detected or approved. The detection query now asks for 100 per page. --- .agents/pm/history/pm-github-v2kt.jsonl | 1 + .agents/pm/issues/pm-github-v2kt.toon | 5 +++-- .github/workflows/release.yml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.agents/pm/history/pm-github-v2kt.jsonl b/.agents/pm/history/pm-github-v2kt.jsonl index 2f6e80b..8bb7c30 100644 --- a/.agents/pm/history/pm-github-v2kt.jsonl +++ b/.agents/pm/history/pm-github-v2kt.jsonl @@ -6,3 +6,4 @@ {"ts":"2026-08-10T16:14:01.418Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/4","value":{"created_at":"2026-08-10T16:14:01.418Z","author":"claude","text":"Round four review fixes. Greptile found that classifying a review thread by its first comment lets a bot opened thread with a substantive human reply be auto resolved, bypassing the human concern. A thread now counts as advisory only when every comment on it is bot authored, and threads with no comments are excluded. Verified against four thread shapes, all bot, bot then human, human only, and empty, selecting only the all bot thread. CodeRabbit found the dist rebuild check omitted ignored matching so newly generated ignored artifacts under dist would stay hidden."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:14:01.418Z"}],"before_hash":"2042d4ef91b97c5533970b13e101ea72b5bb5d1b5430fe11c41f1428dfeb0718","after_hash":"3c3705b49ccf9e87c5c38880c9f76b3a952bc615f9f98c5b67b43a33fad89a3b"} {"ts":"2026-08-10T16:23:19.099Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/5","value":{"created_at":"2026-08-10T16:23:19.099Z","author":"claude","text":"Round five review fixes. CodeRabbit found that the tracking note claimed dist validation that pm-web's workflow did not actually contain. An audit showed seven repositories track dist but had no rebuild check, because the earlier patch keyed on a diff paths idiom those repositories do not use: pm-changelog, pm-context, pm-gantt-chart, pm-graph, pm-jira, pm-ops and pm-web. All seven now rebuild dist from clean and compare with git status including untracked and ignored files, and reproducibility was confirmed locally in each before enabling the check. Greptile found that fetching only the first hundred thread comments lets a human reply past that page escape the all bot predicate, so the filter now refuses to resolve any thread whose comment total exceeds the comments actually returned. Verified against a truncated thread with a total of one hundred fifty and two fetched nodes, which is correctly not resolved."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:23:19.099Z"}],"before_hash":"3c3705b49ccf9e87c5c38880c9f76b3a952bc615f9f98c5b67b43a33fad89a3b","after_hash":"9efe94e308a4cc092b2ee8bc790c2f8a59f3bb9925bfe2e5c5e54332972e336d"} {"ts":"2026-08-10T16:41:09.417Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/6","value":{"created_at":"2026-08-10T16:41:09.417Z","author":"claude","text":"CodeRabbit reported that git ls-remote --tags returns both the tag object and its peeled commit for an annotated tag, so cut -f1 would yield two SHAs and git rev-list would fail. Tested against a real annotated tag pushed to a local bare remote. The peeled entry appears when ls-remote is called with no pattern or with a glob, but NOT with the exact refspec form this workflow uses, which returned a single line. The finding therefore does not reproduce against our call. The suggested form was adopted anyway because it costs nothing, states the intent explicitly, and keeps the code correct if the pattern is ever loosened to a glob."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:41:09.417Z"}],"before_hash":"9efe94e308a4cc092b2ee8bc790c2f8a59f3bb9925bfe2e5c5e54332972e336d","after_hash":"3bfec546b57470fc9666cf1d55906411f8350b7988f4d966b0f8451e2e054341"} +{"ts":"2026-08-10T16:57:16.322Z","author":"claude","author_source":"asserted","agent_harness":"claude-code","agent_instance":"304c86ad9f9d7cbae7f44c3b","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"1","source":"environment"},"topic":null},"op":"note_add","patch":[{"op":"add","path":"/metadata/notes/7","value":{"created_at":"2026-08-10T16:57:16.322Z","author":"claude","text":"Round six follow up. CodeRabbit noted the parked run detection reads only the first page of the workflow runs endpoint, whose default page size is thirty, so a parked run past that page would go undetected. The query now requests one hundred per page. This is a diagnostic path rather than a safety one, since an undetected parked run simply means the release is not approved and times out without publishing, but the larger page costs nothing. Thread level pagination was declined separately because unfetched threads are never resolved and therefore keep blocking, which is the safe direction."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-10T16:57:16.322Z"}],"before_hash":"3bfec546b57470fc9666cf1d55906411f8350b7988f4d966b0f8451e2e054341","after_hash":"ccb2819974d107822f82a38d930bb36e486d490bb0ed619c32057eb6e86bf0bb"} diff --git a/.agents/pm/issues/pm-github-v2kt.toon b/.agents/pm/issues/pm-github-v2kt.toon index 94e65d3..93ebaad 100644 --- a/.agents/pm/issues/pm-github-v2kt.toon +++ b/.agents/pm/issues/pm-github-v2kt.toon @@ -6,9 +6,9 @@ status: open priority: 2 tags: [] created_at: "2026-08-10T15:27:52.482Z" -updated_at: "2026-08-10T16:41:09.417Z" +updated_at: "2026-08-10T16:57:16.322Z" author: pi-agent -notes[7]{created_at,author,text}: +notes[8]{created_at,author,text}: "2026-08-10T15:27:52.883Z",pi-agent,"Applied the pm-beads release ordering fix to .github/workflows/release.yml: added pull-requests write permission, captured base_sha in the decide step, and replaced the publish-then-push-to-main steps with a protected-PR merge, a verify-merged-release check, the npm publish (with idempotence guard), and a tag-only push. dist is gitignored here so it is excluded from the verify diff. Gates npm ci, build, test, release:check and yaml all pass." "2026-08-10T15:30:44.660Z",claude,"Greptile reported that creating the release pull request with GITHUB_TOKEN suppresses its pull_request event so CI never starts. Measured against pm-changelog, the event is not suppressed: a run was created at 04:54:24Z with event pull_request for branch release/2026.8.9. The real blocker is that attempt 1 concluded action_required, meaning GitHub parked the run awaiting workflow approval under the fork pull request contributor approval policy of first_time_contributors, because github-actions had no merged pull request in that repository yet. A parked run never appears in the status check rollup, so it is indistinguishable from a required check that failed. The merge wait now detects parked runs, reports the run URL, attempts approval using the actions write permission, and separates awaiting approval from a failed check in the deadline error." "2026-08-10T15:55:22.540Z",claude,"Round two review fixes. CodeRabbit found that the Push release tag step consulted only the local tag database, while the last tag fetch happens back in Decide release, so a tag created on origin in between would be missed, git tag would succeed locally and the push would be rejected as non-fast-forward after a successful publish, which is exactly the npm ahead of git state this work removes. The step now fetches tags and compares against the remote tag target, exiting cleanly when the tag already points at the verified commit. CodeRabbit also found that the dist entry in the verification diff verified nothing, because nothing rebuilds before it so git diff compared dist against itself and always passed, and git diff cannot see untracked or orphaned artifacts. Where dist is tracked the step now rebuilds from clean and uses git status porcelain with untracked files included. Reproducibility was confirmed locally in every repository that tracks dist before shipping the check." @@ -16,4 +16,5 @@ notes[7]{created_at,author,text}: "2026-08-10T16:14:01.418Z",claude,"Round four review fixes. Greptile found that classifying a review thread by its first comment lets a bot opened thread with a substantive human reply be auto resolved, bypassing the human concern. A thread now counts as advisory only when every comment on it is bot authored, and threads with no comments are excluded. Verified against four thread shapes, all bot, bot then human, human only, and empty, selecting only the all bot thread. CodeRabbit found the dist rebuild check omitted ignored matching so newly generated ignored artifacts under dist would stay hidden." "2026-08-10T16:23:19.099Z",claude,"Round five review fixes. CodeRabbit found that the tracking note claimed dist validation that pm-web's workflow did not actually contain. An audit showed seven repositories track dist but had no rebuild check, because the earlier patch keyed on a diff paths idiom those repositories do not use: pm-changelog, pm-context, pm-gantt-chart, pm-graph, pm-jira, pm-ops and pm-web. All seven now rebuild dist from clean and compare with git status including untracked and ignored files, and reproducibility was confirmed locally in each before enabling the check. Greptile found that fetching only the first hundred thread comments lets a human reply past that page escape the all bot predicate, so the filter now refuses to resolve any thread whose comment total exceeds the comments actually returned. Verified against a truncated thread with a total of one hundred fifty and two fetched nodes, which is correctly not resolved." "2026-08-10T16:41:09.417Z",claude,"CodeRabbit reported that git ls-remote --tags returns both the tag object and its peeled commit for an annotated tag, so cut -f1 would yield two SHAs and git rev-list would fail. Tested against a real annotated tag pushed to a local bare remote. The peeled entry appears when ls-remote is called with no pattern or with a glob, but NOT with the exact refspec form this workflow uses, which returned a single line. The finding therefore does not reproduce against our call. The suggested form was adopted anyway because it costs nothing, states the intent explicitly, and keeps the code correct if the pattern is ever loosened to a glob." + "2026-08-10T16:57:16.322Z",claude,"Round six follow up. CodeRabbit noted the parked run detection reads only the first page of the workflow runs endpoint, whose default page size is thirty, so a parked run past that page would go undetected. The query now requests one hundred per page. This is a diagnostic path rather than a safety one, since an undetected parked run simply means the release is not approved and times out without publishing, but the larger page costs nothing. Thread level pagination was declined separately because unfetched threads are never resolved and therefore keep blocking, which is the safe direction." body: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 930202e..46b5173 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -325,7 +325,7 @@ jobs: # the token often may not, which is harmless - and always surface the # run so the wait is diagnosable. pending_runs="$(gh api \ - "repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${release_commit}" \ + "repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${release_commit}&per_page=100" \ --jq '.workflow_runs[]? | select(.conclusion=="action_required" or .status=="waiting") | .id' \ 2>/dev/null || true)" if [[ -n "$pending_runs" ]]; then