Repository navigation
feat: implement issue #1296 — Standards sweep reuses a same-date sync branch as-is — a narrower second run opens a PR carrying the first run's commits #1299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0786eda
9a10579
f6a4731
6a260c3
b0a67bc
6960ae2
88ce31d
2531990
99ac734
7d5ee7c
5fff4e1
c12a9e2
f6d7729
72af071
6e6da5d
9ac9357
d16be1c
84a10a5
b49e388
f5b541b
25f20c6
9bac1d2
42224a3
44993b6
0b6ba20
bed4692
8c7539a
d90048f
f6ed5e1
94240db
393bee1
2739b53
bb30e22
1bba4b8
838f087
f3f1b7d
305964a
848181d
dd31177
a31c902
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,8 @@ | |
| # | ||
| # 1. checks for an already-open PR on the sync branch (idempotent skip), | ||
| # 2. preflights that the token can write the repo (clear finding on a scope gap), | ||
| # 3. creates (or reuses) a sync branch off the repo's default branch, | ||
| # 3. creates a sync branch off the repo's default branch (an existing one with | ||
| # no open PR is reset to the default-branch tip, never reused as-is), | ||
| # 4. PUTs the verbatim file content onto that branch via the Contents API, | ||
| # 5. opens a labeled pull request. | ||
| # | ||
|
|
@@ -33,7 +34,7 @@ | |
| # repo_slug owner/name of the target repo (e.g. combsmith/markets) | ||
| # path path of the file within the target repo | ||
| # local_file local file whose contents are deployed verbatim | ||
| # branch head branch to create/reuse for the PR | ||
| # branch head branch to create (or reset) for the PR | ||
| # label label applied to the opened PR (e.g. standards-sync) | ||
| # title commit message and PR title | ||
| # body PR body (markdown) | ||
|
|
@@ -113,19 +114,48 @@ | |
| return 1 | ||
| fi | ||
|
|
||
| # 4. Create the sync branch; reuse it if a prior run already created it. | ||
| # 4. Create the sync branch off the default-branch tip. If a prior run already | ||
| # created it (same-date name), it may carry that run's commits — or later | ||
| # pushes to them — which must NOT leak into this run's PR | ||
| # (combsmith/.github#1296). Step 1 found no open labeled PR; if no open | ||
| # PR of any label uses the branch either, force-reset it to the base tip so | ||
| # it holds only what this run writes. Otherwise, or if the probe or reset | ||
| # errors, fail loudly naming the branch rather than build on unknown commits. | ||
| if ! gh api "repos/${repo}/git/refs" --method POST \ | ||
| --raw-field "ref=refs/heads/${branch}" \ | ||
| --raw-field "sha=${base_sha}" --silent 2>/dev/null; then | ||
| if ! gh api "repos/${repo}/git/ref/heads/${branch}" --silent 2>/dev/null; then | ||
| echo "FAILED no-branch" | ||
| return 1 | ||
| fi | ||
| # `gh pr list --head` matches the branch name only, so an open PR from a | ||
| # fork using the same name would match; keep only PRs whose head repo is $repo. | ||
| local branch_pr repo_l="${repo,,}" | ||
| if ! branch_pr=$(gh pr list --repo "$repo" --head "$branch" --state open \ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the fleet contains an organization-owned fork and this sync branch is the head of an open PR against its upstream repository, this probe misses it because Useful? React with 👍 / 👎. |
||
| --json number,headRepository,headRepositoryOwner \ | ||
| --jq "[.[] | select((.headRepositoryOwner.login | ascii_downcase) == \"${repo_l%%/*}\" and (.headRepository.name | ascii_downcase) == \"${repo_l##*/}\")] | .[0].number // \"\"" \ | ||
|
Comment on lines
+134
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Check every matching PR before resetting the branch.
🤖 Prompt for AI Agents
Comment on lines
+134
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fresh evidence after the earlier fork-scope fix is that the replacement query still uses Useful? React with 👍 / 👎. |
||
| 2>/dev/null); then | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the new branch-use probe fails—for example because GitHub returns a 403/5xx response or the jq projection errors— AGENTS.md reference: AGENTS.md:L376-L379 Useful? React with 👍 / 👎. |
||
| echo "FAILED branch-pr-probe-failed:${branch}" | ||
| return 1 | ||
| fi | ||
| if [ -n "$branch_pr" ]; then | ||
|
Check failure on line 141 in scripts/lib/standards-deploy.sh
|
||
| echo "FAILED branch-in-use:${branch}#${branch_pr}" | ||
| return 1 | ||
| fi | ||
| local reset_err reset_msg | ||
| reset_err=$(mktemp) | ||
| if ! gh api "repos/${repo}/git/refs/heads/${branch}" --method PATCH \ | ||
| --raw-field "sha=${base_sha}" -F force=true --silent 2>"$reset_err"; then | ||
|
Comment on lines
+147
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Prevent concurrent runs from resetting each other’s branch. If two runs pass the open-PR check before either creates a PR, one run can reset the branch while the other uploads files or opens its PR. The resulting PR can contain the other run’s files or lose commits that the first run just wrote. Give each run its own branch, or add coordination that covers the check, reset, uploads, and PR creation. The force-reset operation replaces the branch ref rather than preserving intervening commits. (docs.github.com) 🤖 Prompt for AI Agents
Comment on lines
+147
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When two same-day invocations target the same repo/branch outside the Useful? React with 👍 / 👎. |
||
| reset_msg=$(tr '\n' ' ' < "$reset_err"); rm -f "$reset_err" | ||
| echo "FAILED branch-reset-failed:${branch}${reset_msg:+:${reset_msg}}" | ||
| return 1 | ||
| fi | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| rm -f "$reset_err" | ||
| fi | ||
|
|
||
| # 5. PUT each file verbatim onto the branch. The blob SHA is looked up per file | ||
| # on the branch so both create (absent → empty SHA) and update (drifted stub) | ||
| # work, whether the branch is fresh or reused. Errors are NOT swallowed: a | ||
| # work, whether the branch is fresh or reset. Errors are NOT swallowed: a | ||
| # read failure that yields an empty SHA would make the subsequent PUT omit | ||
| # `sha` and 422 against an existing file — the exact opaque failure in #864. | ||
| local path local_file branch_sha encoded j err_file get_rc get_msg put_msg | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,15 @@ args="$*" | |
| case "$sub" in | ||
| pr) | ||
| case "${1:-}" in | ||
| list) printf '%s' "${GH_EXISTING_PR:-}" ;; | ||
| list) | ||
| # --head <branch>: the "is the existing sync branch in use?" probe | ||
| # (any label). Otherwise: the label-keyed idempotency check. | ||
| if printf '%s' "$args" | grep -q -- '--head'; then | ||
| [ "${GH_BRANCH_PR_RC:-0}" -ne 0 ] && exit "${GH_BRANCH_PR_RC}" | ||
| printf '%s' "${GH_BRANCH_PR:-}" | ||
| else | ||
| printf '%s' "${GH_EXISTING_PR:-}" | ||
| fi ;; | ||
| create) | ||
| [ "${GH_PR_CREATE_RC:-0}" -ne 0 ] && exit "${GH_PR_CREATE_RC}" | ||
| printf '%s\n' "${GH_PR_URL:-https://github.com/o/r/pull/1}" ;; | ||
|
|
@@ -33,6 +41,16 @@ case "$sub" in | |
| endpoint="${1:-}" | ||
| case "$endpoint" in | ||
| */git/refs) exit "${GH_REF_CREATE_RC:-0}" ;; | ||
| */git/refs/heads/*) | ||
| # Force-reset of an existing sync branch. When GH_BRANCH_FILES names a | ||
| # file it models the branch's content: a reset to the base tip drops | ||
| # every commit a prior run left on it. | ||
| if [ "${GH_REF_RESET_RC:-0}" -ne 0 ]; then | ||
| [ -n "${GH_REF_RESET_ERR:-}" ] && printf '%s\n' "$GH_REF_RESET_ERR" >&2 | ||
| exit "${GH_REF_RESET_RC}" | ||
| fi | ||
| [ -n "${GH_BRANCH_FILES:-}" ] && printf '' > "$GH_BRANCH_FILES" | ||
| exit 0 ;; | ||
| */git/ref/heads/*) | ||
| if printf '%s' "$args" | grep -q 'object.sha'; then | ||
| printf '%s' "${GH_BASE_SHA-basesha111}" | ||
|
|
@@ -60,6 +78,10 @@ case "$sub" in | |
| printf '%s\n' "${GH_PUT_ERR:-gh: sha was not supplied (HTTP 422)}" >&2 | ||
| exit "${GH_PUT_RC}" | ||
| fi | ||
| if [ -n "${GH_BRANCH_FILES:-}" ]; then | ||
| p="${endpoint#*/contents/}" | ||
| grep -qxF "$p" "$GH_BRANCH_FILES" || printf '%s\n' "$p" >> "$GH_BRANCH_FILES" | ||
| fi | ||
| exit 0 ;; | ||
| repos/*) | ||
| # repos/<repo>: the default-branch lookup AND the preflight write probe | ||
|
|
@@ -193,6 +215,91 @@ deploy() { | |
| grep -q 'gh pr create' "$GH_CALLS" | ||
| } | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Stale same-date branch (combsmith/.github#1296): a prior run's branch — | ||
| # its PR closed — must not leak that run's commits into this run's PR. | ||
| # --------------------------------------------------------------------------- | ||
| @test "stale same-date branch with extra commits and no open PR: PR holds only this run's files" { | ||
| GH_REF_CREATE_RC="1"; export GH_REF_CREATE_RC # branch already exists | ||
| GH_BRANCH_FILES="${TT_TMP}/branch-files"; export GH_BRANCH_FILES | ||
| # The first run's 5 stubs, plus an agent's later "review fix" commit. | ||
| printf '%s\n' .github/workflows/a.yml .github/workflows/b.yml \ | ||
| .github/workflows/c.yml .github/workflows/d.yml .github/workflows/e.yml \ | ||
| > "$GH_BRANCH_FILES" | ||
| run sd_deploy_files_via_pr "combsmith/markets" "standards-sync/workflows-20261011" \ | ||
| "standards-sync" "chore: sync 1 stub" "body" \ | ||
| ".github/workflows/persona-mention.yml" "$LOCAL_FILE" | ||
| [ "$status" -eq 0 ] | ||
| [ "$output" = "OPENED https://github.com/o/r/pull/1" ] | ||
| [ "$(cat "$GH_BRANCH_FILES")" = ".github/workflows/persona-mention.yml" ] | ||
| } | ||
|
|
||
| @test "stale branch is force-reset to the default-branch tip before any PUT" { | ||
| GH_REF_CREATE_RC="1"; export GH_REF_CREATE_RC | ||
| run deploy | ||
| [ "$status" -eq 0 ] | ||
| grep -q 'gh pr list --repo combsmith/markets --head standards-sync/add-to-project --state open' "$GH_CALLS" | ||
| grep -q 'gh api repos/combsmith/markets/git/refs/heads/standards-sync/add-to-project --method PATCH --raw-field sha=basesha111 -F force=true' "$GH_CALLS" | ||
| local reset_line put_line | ||
| reset_line=$(grep -n 'git/refs/heads/.* --method PATCH' "$GH_CALLS" | head -1 | cut -d: -f1) | ||
| put_line=$(grep -n 'contents/.* --method PUT' "$GH_CALLS" | head -1 | cut -d: -f1) | ||
| [ "$reset_line" -lt "$put_line" ] | ||
| } | ||
|
|
||
| @test "a freshly created branch is not reset" { | ||
| run deploy | ||
| [ "$status" -eq 0 ] | ||
| [ "$(grep -c -- '--method PATCH' "$GH_CALLS")" -eq 0 ] | ||
| [ "$(grep -c 'gh pr list .*--head' "$GH_CALLS")" -eq 0 ] | ||
| } | ||
|
|
||
| @test "existing branch used by an open (unlabeled) PR fails loudly naming the branch" { | ||
| GH_REF_CREATE_RC="1"; export GH_REF_CREATE_RC | ||
| GH_BRANCH_PR="77"; export GH_BRANCH_PR | ||
| run deploy | ||
| [ "$status" -ne 0 ] | ||
| [ "$output" = "FAILED branch-in-use:standards-sync/add-to-project#77" ] | ||
| [ "$(grep -c -- '--method PATCH' "$GH_CALLS")" -eq 0 ] | ||
| [ "$(grep -c 'contents/.* --method PUT' "$GH_CALLS")" -eq 0 ] | ||
| [ "$(grep -c 'gh pr create' "$GH_CALLS")" -eq 0 ] | ||
| } | ||
|
|
||
| @test "existing branch whose PR probe errors is not reset" { | ||
| GH_REF_CREATE_RC="1"; export GH_REF_CREATE_RC | ||
| GH_BRANCH_PR_RC="1"; export GH_BRANCH_PR_RC | ||
| run deploy | ||
| [ "$status" -ne 0 ] | ||
| [ "$output" = "FAILED branch-pr-probe-failed:standards-sync/add-to-project" ] | ||
| [ "$(grep -c -- '--method PATCH' "$GH_CALLS")" -eq 0 ] | ||
| [ "$(grep -c 'contents/.* --method PUT' "$GH_CALLS")" -eq 0 ] | ||
| } | ||
|
|
||
| @test "a failed reset of the stale branch fails loudly naming the branch" { | ||
| GH_REF_CREATE_RC="1"; export GH_REF_CREATE_RC | ||
| GH_REF_RESET_RC="1"; export GH_REF_RESET_RC | ||
| run deploy | ||
| [ "$status" -ne 0 ] | ||
| [ "$output" = "FAILED branch-reset-failed:standards-sync/add-to-project" ] | ||
| [ "$(grep -c 'contents/.* --method PUT' "$GH_CALLS")" -eq 0 ] | ||
| [ "$(grep -c 'gh pr create' "$GH_CALLS")" -eq 0 ] | ||
| } | ||
|
|
||
| @test "a failed reset surfaces the API error text when one is available" { | ||
| GH_REF_CREATE_RC="1"; export GH_REF_CREATE_RC | ||
| GH_REF_RESET_RC="1"; export GH_REF_RESET_RC | ||
| GH_REF_RESET_ERR="gh: Cannot force-push (HTTP 422)"; export GH_REF_RESET_ERR | ||
| run deploy | ||
| [ "$status" -ne 0 ] | ||
| [ "$output" = "FAILED branch-reset-failed:standards-sync/add-to-project:gh: Cannot force-push (HTTP 422) " ] | ||
| } | ||
|
|
||
| @test "the branch-in-use probe is scoped to PRs whose head repo is the target repo" { | ||
| GH_REF_CREATE_RC="1"; export GH_REF_CREATE_RC | ||
| run deploy | ||
| [ "$status" -eq 0 ] | ||
| grep -q 'headRepositoryOwner' "$GH_CALLS" | ||
|
Comment on lines
+296
to
+300
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This test never passes same-named fork and target-repository PR records through the new jq predicate: the fake AGENTS.md reference: AGENTS.md:L139-L142 Useful? React with 👍 / 👎. |
||
| } | ||
|
Comment on lines
+296
to
+301
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win Exercise the head-repository filter in this test. The stub returns 🤖 Prompt for AI Agents |
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Update path: file already present on the branch → PUT carries its blob SHA | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an open stacked PR targets the stale sync branch as its base, this
--head-only probe returns no match and the subsequent force-reset changes that PR's comparison base, potentially adding the stale standards commits to its diff or otherwise altering its contents. The installedgh pr list --helpdocuments--headas “Filter by head branch” and provides a separate--basefilter, so check both roles before deciding the branch is unused.Useful? React with 👍 / 👎.