Repository navigation
feat: merge and lifecycle actions on a linked pull request (A5b) - #667
Merged
Merged
Conversation
…rom a host-owned draft, reply, resolve, react, edit, labels, reviewers
…scope, labels, stale head, no retry, the host's review draft
…ments against the diff, and read the account's rights, labels and reviewers
…view draft in Files, review sheet, thread reply and resolve, reactions, edits, labels and reviewers
…rom the host: a fresh action state before each, stack routing, merge-message credit cleaning, a linked revert
…ry action, menu group, merge dialog, confirmations, results, the composer message for conflicts and checks, and the credit-cleaning setting
…de id, keep the action state for the read TTL, and fold the lost merge answer into the unanswered-write test
…d, offer branch updates by their own rule, prefer the fresh action state, and resolve the project and stack layer once
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A5b of #641: merge and the lifecycle actions on a linked pull request. The host reads a fresh
PullRequestActionStatebefore a merge or a branch update (the other lifecycle writes name the pull request by its cached node id), routes native-stack layers away from a single-PR merge, sends each mutation once, and answers in domain terms; the UI ranks one primary action from that state and adds the lifecycle group to the shared row menu.PullRequestRead::ActionState→PullRequestActionState(head, merge state, behind count, merge queue, enabled methods, auto-merge allowed/armed, queue entry, failing check names, pending count,can_update/can_update_branch/can_merge); actionsReadyForReview,ConvertToDraft,Close,Reopen,Revert,UpdateBranch{head,rebase},Merge{head,method,auto,remove_credits},DisableAutoMerge; resultsUpToDate,Queued,AutoMergeEnabled,Opened; rejectionsInStack,StackUnknown; wire note abovePROTOCOL_VERSION.PullRequestMergeMethod;stack_route(links, key)→ (route, layer), the one fold the host checks before a write and the UI ranks with (the layer gives the UI a stack layer's state and URL); settingsproject_merge_methods(set only from the merge dialog's checkbox) andremove_agent_credits_on_merge(Settings → Source Control), patchesProjectMergeMethod,RemoveAgentCreditsOnMerge.READ_TTL, 60 s; a merge and a branch update read it uncached; ready/draft/close/reopen/disable auto-merge/revert send by the cached node id), the lifecycle mutations,merge_message::remove_agent_credits(port of upstreammergeMessage.ts). A lifecycle write drops all of the pull request's reads, on top of main'sdrop_written.pull_requests/lifecycle.rs): rank conflicts → ready → failing checks → auto-merge-on / queued chips → Merge on GitHub (stack) → update branch (whenBEHIND) → enable auto-merge (checks running) → merge; SubBar button on desktop, full-width row on phone; the ⋯ group added once torow_menu(list rows build it from the snapshot; the host reads the rest before writing); the group's writes are disabled while one is in flight or after anUncertainanswer until GitHub is read again, on list rows and the detail alike; Update branch / Update with rebase offered by their own rule (behind, may update the branch, not conflicting, single route), drafts included; the fresh action state speaks over the snapshot for conflicting / failing / pending; "Merge now…" hidden when conflicting and disabled with the blocked reason when GitHub says Blocked; confirmations for close, revert, rebase; the merge dialog with the facts, method track, project-default and credit checkboxes and notes; one toast slot per pull request, shared with A5a's conversation writes (PullRequestWrite); "Ask the agent in this thread" appends an editable message to the thread's composer and sends nothing, and is disabled for a settled or archived thread as well as a read-only one (conversation_read_onlycovers only native subagent mirrors, so the UI checks the thread's settled/archived state itself); behind-base chip with the update choices.Closes #641
Evidence
main(A4): the SubBar has Back · refresh · Open on GitHub · ⋯ and no lifecycle action:After: desktop, light, the same synthetic profile (
acme-sample/payments-api#413, a seeded action state; conversation reads fail because the repository does not exist): primary "Create a merge commit" and the merge confirmation (Squash chosen, credit cleaning on):phone, light, PR page with the primary action:
Tests through the real client against the in-process HTTP fixture (
crates/services/tests/pull_request_reads.rs), new:a_merge_reads_the_head_fresh_and_tells_merged_queued_and_armed_apart: exactmergePullRequest/enablePullRequestAutoMergeinputs withexpectedHeadOid; merged vs auto-merge armed vs merge queue reported distinctly from GitHub's answer; a method the repository does not enable →Invalid, nothing sent; a head moved after the display read rejectedStaleHeadbefore any mutation (which is what proves the merge's read is fresh).credits_leave_the_merge_message_only_when_it_has_some:commitBodyonly when cleaning changed GitHub's message; people stay credited; no message read for rebase.a_branch_update_carries_the_head_and_is_nothing_when_not_behind: MERGE by default, REBASE on request, stale head rejected,UpToDatewith no mutation.lifecycle_writes_name_the_pull_request_and_a_revert_opens_one: ready/draft/close/reopen/disable/revert payloads name the pull request's node id; revert answersOpened.a_write_left_unanswered_is_uncertain_and_never_sent_again, extended: a merge whose connection drops and a revert answer that names no pull request areUncertain, each sent once.crates/runtime/src/app/pull_requests_tests.rs::a_stack_layer_is_never_merged_alone_and_a_revert_is_linked_as_created: unknown stack and a native layer reject merge and update branch with no request at all; a revert's PR is linked with sourcecreated; the sync reads after the writes.merge_message::tests::credits_quoted_in_code_and_people_on_githubs_noreply_address_stay: fences, indented quotes and a person on GitHub's noreply address survive.Tests deleted or rewritten (CONTRIBUTING, When a test fails, or is met on the way, step 2):
a_merge_left_unanswered_is_uncertain_and_never_sent_againdeleted: its lost-answer case is folded into A5a'sa_write_left_unanswered_is_uncertain_and_never_sent_again; its 502 case duplicated that test's 502 contract (labels) through the sameansweredpath.reads_of("PullRequestActionState") == 6assertions in the merge and lifecycle tests removed: they counted reads, an implementation detail; the payload assertions and the stale-head case carry the contracts. The lifecycle test was renamed (it no longer reads the action state).pull_requests_tests.rs: the A5b test's fixture now answers the REST pull request read (the node id comes from it); the two copies of the ack-wait loop became oneackedhelper.Deferred: the merge dialog's ChangedNotice (head, checks or mergeability changing while the dialog is open) is not built; the guard is the host's fresh read and stale-head rejection at confirm, which rejects a moved head with nothing sent ("Not merged: #n changed").
Checks on the final commit:
Live, through the host's
PullRequestReadsagainst a disposable public repository in this account (Tryanks/tcode-a5b-live-…, two rounds):GitHub's squash body for the PR was
Co-authored-by: Tryanks …+Co-authored-by: Claude <noreply@anthropic.com>; the merged commit kept only the first. Round 1 (#1–#3) did the same sequence and alsoRevert→Opened #3. From the app on a synthetic profile: Update branch on #5 → toast "Updated feature-4 with main"; the primary then became "Create a merge commit" onceciwas green.The live runs above predate the review corrections; the simple writes now send by the cached node id and were checked only through the fixture after the change.
Platform gaps: the repository's required check was satisfied by admin bypass (
enforce_admins: false) in round 2, so a blocked merge was not exercised live; blocked and queued merges and native stacks are fixture-only (no organization merge queue or native stack is available to this account).Merge Danger
Door: two-way
Blast Radius: pull-requests
Every lifecycle write goes to GitHub as the user's account: merge, close and revert are real. Each is gated by a fresh head check, a single attempt and, for merge, close, revert and rebase, a confirmation. The wire changes under the current
PROTOCOL_VERSIONwith a note.