7.9 KiB
status, system, requirements, created, owners
| status | system | requirements | created | owners | ||
|---|---|---|---|---|---|---|
| current | integrations |
|
2026-08-24 |
|
GitHub PR Merge Queue System Design
Purpose and boundaries
The integration system owns the GitHub merge operation and the normalized merge-queue state. The task system consumes a bounded pull-request summary. The UI renders the integration state but does not own a second queue contract.
This design covers the complete vertical path. It includes the GitHub clients, storage, task projection, HTTP response, frontend types, React surfaces, and desktop and mobile behavior.
Requirement mapping
| Requirement | Design sections |
|---|---|
REQ-INTEGRATIONS-GITHUB-PR-MERGE-QUEUE-001 |
Components, Data, Control flow, Failure, Presentation |
Components and responsibilities
GitHub provider boundary
apps/backend/internal/github/client.godefines the typed merge outcome.gh_client.goandpat_client.gocall GitHub's asynchronous merge endpoint.graphql.goreads and normalizesmergeQueueEntrystate and metadata.
Integration service and storage
service_pr.goroutes the merge request through workspace-scoped write credentials and invalidates stale pull-request data after success.controller.goexposes the existing merge endpoint and returns the typedmergedorqueuedresult.service_pr_watch.goapplies authoritative queue observations and publishes the normal pull-request update.models.goowns the normalizedPRStatusand persistedTaskPRfields.store.goowns thegithub_task_prsschema and all queue-field write paths.
Task projection
apps/backend/internal/task/statussummaryreduces full pull-request state to a bounded task status.apps/backend/internal/backendapp/status_summary_adapter.gosupplies the stored queue state during live updates and restart hydration.
Web application
apps/web/lib/api/domains/github-pr-api.tstypes the merge result.apps/web/lib/types/github.tsdefines the queue-state andTaskPRfields.pr-merge-button.tsxcontrols eligibility, submission, feedback, and refresh.pr-merge-queue-status.tsxnormalizes queue presentation and duration copy.- The task icon, status chip, summary, and detail components reuse that presentation model.
Data and contracts
github_task_prs stores the last authoritative queue observation:
merge_queue_state TEXT NOT NULL DEFAULT ''stores the normalized GitHub state. Supported values arequeued,awaiting_checks,mergeable,unmergeable, andlocked.merge_queue_position INTEGER NULLstores GitHub's one-based position.merge_queue_estimated_time_to_merge_seconds INTEGER NULLstores an optional non-negative duration.
An empty state with null metadata means that no active queue entry is stored.
The same three fields appear in the TaskPR HTTP, boot, and WebSocket payloads.
The bounded task projection reduces active membership to queued.
The existing merge endpoint remains:
PUT /api/v1/github/prs/:owner/:repo/:number/merge?workspace_id=:workspaceId
Content-Type: application/json
{"merge_method":"squash"}
merge_method is optional. It accepts merge, squash, or rebase. A
successful response is one of these objects:
{"status":"merged"}
{"status":"queued"}
The provider request uses merge_action=default. GitHub therefore selects the
allowed direct or queued path.
Control flow
Merge request
- The frontend derives eligibility from the current pull-request state.
- The user activates the existing merge action.
- The service selects workspace-scoped personal-write credentials.
- The provider starts or resumes GitHub's asynchronous merge request.
- The provider polls a pending request until GitHub returns a terminal result.
- The controller returns
mergedorqueuedto the frontend. - The frontend shows outcome-specific feedback and requests a state refresh.
Only GitHub's enqueued result maps to queued. An already merged pull request
maps to merged. A provider failure remains retryable.
Queue observation
The batched GraphQL selection reads this field:
mergeQueueEntry { state position estimatedTimeToMerge }
graphql.go records whether the response observed queue membership. This guard
distinguishes an authoritative null from REST and gh pr view responses that
cannot read queue data.
SyncTaskPR atomically replaces the queue state, position, and estimate after
an authoritative GraphQL observation. It preserves all three fields after a
queue-unaware read. It clears them after an authoritative null, merge, or close.
The update then publishes github.task_pr.updated through the existing path.
State transitions
- An open pull request with no observed queue entry is not queued.
- A non-null authoritative entry creates or updates active queue membership.
- A later authoritative null removes active queue membership.
- A merged or closed state clears queue metadata and wins presentation priority.
- A queue-unaware or failed read preserves the last complete observation.
- An unknown future non-empty state remains active and uses generic UI copy.
Failure and recovery
Missing credentials, permission errors, provider validation errors, rate limits, and transport errors return a non-success response. These errors do not change the stored pull-request state or show a successful outcome.
An unknown successful provider result fails closed. Kandev does not claim that the pull request merged or entered the queue.
A failed GraphQL queue read preserves the last complete observation. The next successful authoritative sync updates or clears it. This behavior also protects queue state during restart recovery and mixed GraphQL, REST, and CLI refreshes.
Persistence
The queue fields use additive SQLite columns in github_task_prs. Fresh schema,
migration, create, replace, restore, and update paths round-trip the complete
entry. Existing rows start with no queue entry and converge after a successful
GraphQL status sync.
The estimate remains a duration. Kandev does not convert it to a durable target timestamp because GitHub can change the estimate on each observation.
Security
The merge action uses the active workspace's personal-write GitHub route. It requires the same pull-request permissions as GitHub's merge API. Kandev does not bypass repository rules or elevate the user.
Presentation and responsive behavior
An active queue entry uses #966600. Terminal states take precedence. Queue
membership takes precedence over other non-terminal icon states.
The task hover summary, desktop status popover, phone drawer, and pull-request detail panel show localized state and position. Estimates below one minute use a localized sub-minute label. Estimates of one minute or more round up to a localized whole-minute duration. Missing estimates omit estimate text instead of inventing a value. Unknown provider states use generic queue copy.
Desktop and mobile share state derivation and actions. Mobile uses the existing Review surface and status drawer. The action has a touch-sized target, does not depend on hover, and does not create document-level horizontal overflow.
Verification evidence
Focused backend coverage exists in the GitHub client, GraphQL conversion, storage, synchronization, and task-status packages. Frontend unit coverage exists for merge eligibility, merge outcomes, queue formatting, and status priority. Playwright coverage exists in:
apps/web/e2e/tests/pr/pr-merge-queue.spec.tsapps/web/e2e/tests/pr/mobile-pr-merge-queue.spec.ts