MR creation not idempotent on resume — GitLab 409 when re-running after auto-merge failure #280
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Follow-on bug from #274. After #274's
glab mr view --jsonfailure aborts themrstage during auto-merge polling (i.e. after the MR was already created), resuming the run causesrunMrStageto attempt MR creation again, which GitLab rejects:Root cause
Two compounding gaps in
src/mr/services/runMrStage.ts:mr-result.jsonis written only after the entireresolveMrOutcome()call succeeds — includingcreateMr()and the fullrunAutoMerge()block (poll + merge). IfcreateMr()succeeds but auto-merge subsequently throws (e.g. the #274glab --jsonbug, or any other transient auto-merge failure),mr-result.jsonis never written even though the MR/PR genuinely exists on the platform.Resume treats a missing
mr-result.json/mr-skipped.jsonas "mr stage not done" (findIncompleteRun,determineResumeStageWithGit), so the wholemrstage reruns from scratch.ensureBranchCheckedOut/ensureCommitted/ensurePushedare all correctly idempotent (see their doc comments), butcreateMr()has no equivalent "does an MR already exist for this branch?" check — it unconditionally issuesPOST .../merge_requestsagain. GitLab rejects a second MR for the same source→target branch pair with409("Another open merge request already exists for this source branch"), whichclassifyHttpStatusmaps tokind: 'unknown', retriable: false.This is inconsistent with the research stage's shipping workflow (AGENTS.md), which is explicitly documented as idempotent: "skip when the research PR is already merged; reference an existing open PR instead of duplicating." The MR stage has no analogous check.
Likely affects GitHub (422 "A pull request already exists...") and Forgejo similarly, not just GitLab — the missing idempotency is in
runMrStage's orchestration, not GitLab-specific.Fix (both parts requested — belt and suspenders)
1. Make
createMridempotent across resumeBefore calling
client.createMr(), check whether an open MR/PR already exists forsourceBranch -> targetBranchon the platform and reuse it instead of creating a duplicate:GET /projects/:id/merge_requests?source_branch=...&target_branch=...&state=openedGET /repos/:owner/:repo/pulls?head=:owner:branch&base=...&state=openIf found, skip the create call and use the existing MR/PR's URL/number to continue (auto-merge etc.). This should live behind the
MrClientinterface (e.g. a newfindExistingMrmethod or fold the check intocreateMr) so all three platform clients implement it consistently, with unit tests per platform (found / not-found / API-error passthrough).2. Persist MR-created state before entering auto-merge
Write
mr-result.json(or a new durable marker) immediately aftercreateMr()succeeds, before startingrunAutoMerge(). This way:createMr().This likely needs a schema/shape decision (e.g.
mr-result.jsonwritten in two phases — created-but-not-merged, then updated withautoMergefields once merge completes — or a separate intermediate artifact). Needs care to keepmr-result.json's schema stable perAGENTS.md's "Artifacts are the contract" rule; if the schema must change, updateDESIGN.md/IMPLEMENTATION_PLAN.mdand tests together.Doing both closes the gap from either direction: idempotent creation prevents the 409 even if resume detection doesn't improve, and accurate stage-completion tracking prevents unnecessary re-creation attempts even if the idempotency check has a platform-specific gap.
Affected files
src/mr/services/runMrStage.ts(orchestration, phased artifact write)src/mr/types/mrClient.ts(interface — possible new method)src/gitlab/services/gitLabMrClient.tssrc/github/services/gitHubMrClient.tssrc/forgejo/services/forgejoMrClient.tssrc/run/services/findIncompleteRun.ts,src/run/services/determineResumeStage.ts(if a new intermediate artifact/marker is introduced)*.test.tsfiles for all of the aboveAcceptance criteria
createMr()for a branch that already has an open MR/PR reuses the existing one instead of failing with a duplicate/conflict error (GitLab 409, GitHub 422, Forgejo equivalent), covered by unit tests for all three platforms.mr-result.jsonwithout updatingDESIGN.md/IMPLEMENTATION_PLAN.mdand tests together.npm run lintandnpm testpass.Related
glab mr view --jsonbug that originally triggered this failure path)