GitLab Commit Status Management#
Atlantis posts commit statuses to GitLab via POST /projects/:id/statuses/:sha (GitLab Commits API) to reflect plan/apply progress on merge requests. The implementation lives in UpdateStatus in server/events/vcs/gitlab/client.go. Because GitLab's pipeline model is more complex than GitHub's, this integration has several non-obvious behaviors around ref keying, pipeline ownership, and status filtering.
API Mechanics: ref and pipeline_id#
The POST /projects/:id/statuses/:sha endpoint is find-or-create, keyed on the ref string . Two requests posting a status to the same commit SHA but using different ref values β for example, main versus refs/heads/main β will create two separate pipelines. GitLab's own docs note: "when duplicate pipelines exist for the same commit, it can be ambiguous which pipeline receives the external status" .
The optional pipeline_id parameter can disambiguate: if provided, GitLab attaches the status to that specific pipeline rather than creating a new one .
Atlantis's pipeline lookup strategy#
UpdateStatus resolves which pipeline to target in this order :
- Get the commit via
GET /projects/:id/repository/commits/:shaand readcommit.LastPipeline.ID. - If a pipeline is found, set
PipelineIDin the status request β attaching the status to that existing pipeline. - If no pipeline is found after retries, fall back to setting
Ref = pull.HeadBranch(the bare branch name, e.g.,main), which triggers GitLab to find-or-create a pipeline keyed on that ref.
Retry behaviour is controlled by the StatusRetryEnabled flag: with it disabled (default), Atlantis tries up to 2 times with a fixed 2-second delay; with it enabled, up to 5 times with jitter backoff (2β5 s) . This flag was introduced by PR #5986.
Known Issues#
1. Orphaned pipelines from third-party integrations (Issue #6871)#
When a third-party tool (e.g., CodeRabbit) posts a commit status using a fully-qualified ref (refs/heads/<branch>) between Atlantis's first and second status writes, GitLab creates a second pipeline under that ref. On its next write, Atlantis calls GetCommit again, finds this newer pipeline, and silently adopts it β posting all subsequent statuses against the wrong pipeline. The original Atlantis-created pipeline is abandoned forever in running state. See Issue #6871.
These orphaned pipelines accumulate silently because they contain no jobs (GitLab's timeout handling never fires), and can only be removed with DELETE /pipelines/:id as an Owner.
StatusRetryEnabled / PR #5986 narrows the timing window (finding a pipeline before creating one reduces the chance of a second pipeline appearing), but does not validate ownership or ref match after adoption. The underlying defect β adopting commit.LastPipeline.ID without checking commit.LastPipeline.Ref against pull.HeadBranch β remains open.
2. Cross-MR status leakage when multiple MRs share a HEAD commit (Issue #5228)#
When two merge requests use different branches but point to the same commit SHA, Atlantis creates a pipeline for the first MR but reuses it for the second without creating a separate one. The second MR's pipeline status never reaches a terminal state, which can permanently block merge if the project enforces pipeline success. See Issue #5228.
Mergeability Evaluation and Status Filtering#
PullIsMergeable in client.go paginates all commit statuses for the HEAD SHA (GET /projects/:id/repository/commits/:sha/statuses, 100 per page) and applies a multi-layer ref filter to avoid incorrectly blocking merges:
- MR-owned refs (
refs/merge-requests/<iid>/head,refs/merge-requests/<iid>/merge) are always trusted . - If any MR-ref status exists for the SHA, statuses from other branches/refs are discarded β preventing a failed plan on one MR from blocking another MR that happens to share the same commit .
- If no MR-ref status exists, the filter falls back to the source branch name, preserving compatibility with external CIs that post against the branch .
- Atlantis-owned
apply,policy_check,pre_workflow_hook, andpost_workflow_hookstatuses are skipped viaisSkippableAtlantisCommitStatusto prevent self-blocking on apply .
This filtering logic was substantially improved by PR #6557 (adds MR-ref-scoped filtering) and PR #6369 (expands skippable Atlantis statuses).
GitLab version compatibility#
Mergeability uses DetailedMergeStatus on GitLab β₯ 15.6 (accepting "mergeable", "ci_still_running", "ci_must_pass") and falls back to the legacy merge_status == "can_be_merged" on older versions .
Quick Reference#
| Topic | Location |
|---|---|
UpdateStatus β pipeline lookup & retry | client.go:567-694 |
PullIsMergeable β status pagination & filtering | client.go:322-476 |
isSkippableAtlantisCommitStatus | client.go:478-492 |
| GitLab Set Commit Status API | GitLab Docs |
| Orphaned pipeline issue | Issue #6871 |
| Cross-MR leakage issue | Issue #5228 |
gitlab-status-retry-enabled flag | PR #5986 |
| MR-ref scoped status filtering | PR #6557 |
| Expanded skippable statuses fix | PR #6369 |