tbd_ship: idempotent re-invoke (resume an existing branch/PR) instead of colliding #48

Closed
opened 2026-07-03 22:41:49 +00:00 by mathias · 1 comment
Owner

Context

tbd_ship (v0.6.0, #40) derives a deterministic branch tbd/<slug>-<hash> from the change. With ci_timeout_seconds=0 (default) it returns pending/PR-only right after opening the PR, and the issue's UX is "let the caller poll or re-invoke". But re-invoking the same change collides: CreateBranch fails because the branch already exists, so the second call errors instead of resuming.

Desired behavior

Make tbd_ship idempotent / resumable: if the derived (or provided) branch + its PR already exist for this change, skip creation and jump straight to the CI gate + merge decision on the existing PR's head commit. So a caller can:

  1. tbd_ship(...)pending (CI not done)
  2. tbd_ship(...) again later → same branch/PR detected → re-evaluate CI → merge if now green.

Sketch

  • On CreateBranch conflict (or a pre-check via branch_list/pr_list head=branch), don't error — look up the open PR for head=branch, use its head sha, run the gate, merge-or-report.
  • Only (re)write the file if the content differs from what's already on the branch (the sha-fetch path already exists).
  • Keep the first-call path unchanged.

Acceptance

  • Second tbd_ship with the same change resumes the existing PR and merges when CI is now green (no "branch exists" error).
  • Content unchanged → no redundant commit.
  • First call behavior unchanged; tests for the resume path.

Refs

Follow-up to #40 (v0.6.0). internal/tools/tbd_ship.go.

## Context `tbd_ship` (v0.6.0, #40) derives a deterministic branch `tbd/<slug>-<hash>` from the change. With `ci_timeout_seconds=0` (default) it returns `pending`/PR-only right after opening the PR, and the issue's UX is "let the caller poll or re-invoke". But **re-invoking the same change collides**: `CreateBranch` fails because the branch already exists, so the second call errors instead of resuming. ## Desired behavior Make `tbd_ship` idempotent / resumable: if the derived (or provided) branch + its PR already exist for this change, **skip creation and jump straight to the CI gate + merge decision** on the existing PR's head commit. So a caller can: 1. `tbd_ship(...)` → `pending` (CI not done) 2. `tbd_ship(...)` again later → same branch/PR detected → re-evaluate CI → merge if now green. ## Sketch - On `CreateBranch` conflict (or a pre-check via `branch_list`/`pr_list head=branch`), don't error — look up the open PR for `head=branch`, use its head sha, run the gate, merge-or-report. - Only (re)write the file if the content differs from what's already on the branch (the sha-fetch path already exists). - Keep the first-call path unchanged. ## Acceptance - [ ] Second `tbd_ship` with the same change resumes the existing PR and merges when CI is now green (no "branch exists" error). - [ ] Content unchanged → no redundant commit. - [ ] First call behavior unchanged; tests for the resume path. ## Refs Follow-up to #40 (v0.6.0). `internal/tools/tbd_ship.go`.
Author
Owner

Done in v0.7.0.

tbd_ship is now idempotent / resumable:

  • CreateBranch conflict tolerated — a re-invoke on an existing branch resumes instead of erroring.
  • Existing PR reused — if CreatePullRequest reports one already open for the head (409/422), it's resolved via ListPullRequests (matching head.ref).
  • No redundant commit — if the branch already has byte-identical content at the path, the write is skipped.
  • Then the same fail-closed CI gate runs and merges if now green.

So the #40 "poll or re-invoke" UX actually works: first call returns pending; a later call with the same change resumes the PR and merges once CI is green — no "branch exists" error.

Test: TestTBDShip_Resume_ExistingBranchAndPR (branch+PR exist, content unchanged → no write, merges when green). First-call paths unchanged; task check green (exit 0, 0 FAIL — verified, not grep-filtered).

Done in v0.7.0. `tbd_ship` is now idempotent / resumable: - **`CreateBranch` conflict tolerated** — a re-invoke on an existing branch resumes instead of erroring. - **Existing PR reused** — if `CreatePullRequest` reports one already open for the head (409/422), it's resolved via `ListPullRequests` (matching `head.ref`). - **No redundant commit** — if the branch already has byte-identical content at the path, the write is skipped. - Then the same fail-closed CI gate runs and merges if now green. So the #40 "poll or re-invoke" UX actually works: first call returns `pending`; a later call with the same change resumes the PR and merges once CI is green — no "branch exists" error. Test: `TestTBDShip_Resume_ExistingBranchAndPR` (branch+PR exist, content unchanged → no write, merges when green). First-call paths unchanged; `task check` green (exit 0, 0 FAIL — verified, not grep-filtered).
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/gitea-mcp#48