GitLab
Served from GitLab's own pinned OpenAPI document through the catalog provider, with no adapter code per endpoint. The native GitLab adapter that source release v0.2.0 packaged was retired in v0.11.0, once every operation it carried ran through the catalog with the same or stricter guarantees.
Follow a request from your laptop to GitLab to see federation, the separate authentication boundaries and the issue response.
What runs
The repository ships a reviewed selection set for GitLab. Each entry exposes one operation of the pinned source under a local id with a declared effect:
| id | effect | what it binds |
|---|---|---|
project.get, issues.list, file.get, branch.get | read | one GET each on the project, its issues, a repository file, a branch |
projects.list, project.events | read | visible projects, one project's events |
tags.list, releases.list, commits.list, repository.compare | read | repository tags, releases, commits for a ref, comparison of two refs |
repository.tree, commit.get, commit.diff, branches.list | read | the files under a path at a ref, one commit, its file diffs, the branches with their head commits |
deployments.list | read | a project's deployments with their environment |
merge_requests.list, merge_request.get | read | the project's merge requests, one merge request by IID |
merge_request.diffs, merge_request.discussions | read | one merge request's file diffs, its discussions with their notes, each by IID and one page per call |
search.blobs | read | project code search, scope held to blobs; the path comes from a cited source correction (adapters/gitlab/upstream/openapi_v3.amendments.json) |
tag.get, release.get, release.links | read | one tag, one release, a release's asset links, each by tag name |
tag.create | write | one POST, guarded: the ref must resolve to the caller's sha; proven by the tag at that commit |
tag.delete | write | one DELETE, guarded on the tag at the caller's sha; proven by a read answering 404 |
release.create, release.update | write | one POST or PUT on an existing tag at the caller's sha; proven by the release at that tag and commit |
pipelines.list, pipeline.get, pipeline.jobs, job.get | read | pipelines, one pipeline, its jobs, one job |
job.trace | read | a job's log, answered as text |
environments.list | read | a project's environments, by name, search or state |
pipeline.retry, pipeline.cancel | write | one POST, guarded on the pipeline at the caller's sha; proven by the answer naming that pipeline at sha. A pipeline with nothing to retry or cancel is reported applied: its status says what happened |
pipeline.create | write | one POST running a pipeline for body.ref, guarded on that ref at the caller's sha; proven by a pipeline for that ref at sha |
commit.create, file.update | write | one commit onto an existing branch, guarded on its head at the caller's sha; proven by the new commit's first parent being sha. Neither creates a branch: use branch.create first |
branch.create | write | one POST, guarded on body.ref at the caller's sha; proven by the branch at that commit |
branch.delete | write | one DELETE, guarded on the branch at the caller's sha; proven by a read answering 404 |
project.create | write | one POST, guarded on the namespace path and body.namespace_id naming the same namespace; proven by the project in it. No namespace read is selected, so the caller supplies the id |
issue.create | write | one POST opening an issue, unguarded |
merge_request.create | write | one POST, guarded on the pinned source head |
merge_request.update | write | one PUT, guarded on the pinned source head |
merge_request.merge | write | one PUT, guarded on five checks |
merge_request.auto_merge | write | one PUT with auto_merge fixed to true, guarded on the pinned head and an open merge request; proven by auto-merge set or merged |
merge_request.reopen | write | one PUT with state_event fixed to reopen, guarded on a closed merge request at the pinned head; proven opened at that head |
merge_request.discussion.get | read | one discussion of a merge request by id |
merge_request.note.create, merge_request.discussion.reply | write | one POST adding a note or a discussion reply, unguarded |
merge_request.discussion.resolve | write | one PUT resolving or unresolving a discussion, guarded on it being the requested, resolvable discussion and on the answered id and state |
feed.containers, feed.items | read | the merge-request feed: member projects, then one project's merge requests changed since a watermark |
More columns: swipe horizontally, or focus the table and use the arrow keys.
Input is one property per declared path or query parameter, named as GitLab names it, plus a
body object where the operation takes one. Output is the provider's status, its body unchanged,
and provenance naming the instance, the resource path and the pinned source's SHA-256. A paged
read returns one page as GitLab answers it, and every list read bounds per_page to 1 through 100.
The merge-request feed
GitLab binds the shared feed family as profile
gitlab-merge-requests/1 (GitLab feed profile).
feed.containers lists the projects the token's user is a member of, oldest project id first,
continued after the last id of a full page. feed.items reads one project's merge requests in
every state, oldest change first, and resumes from the watermark the previous page returned. The
revision is the merge request's updated_at. Every project is listed private, because a public
project may keep its merge requests to its members; url is null, because a project rename
changes a merge request's link without changing its revision; deleted merge requests are not
observed. The binding is checked against recorded GitLab answers and a stand-in provider; it has
not been read from a running GitLab.
Writes and the guard
A write is a required-approval mutation under private protocol two, with the host's approval, audit and attempt controls (the local runtime). The approval policy must name each write before an approval is issued for it, and the approval binds the whole input by digest. The guard is data in the selection: before the one request it reads the merge request and refuses unless every check holds; after it, it compares the response and leaves the outcome uncertain, never refused, when a check fails, because GitLab may already have applied the write. No corrective request is ever issued.
The merge guard requires the merge request to be open and mergeable, at the pinned body.sha,
with the pinned pipeline_id as its head pipeline in status success; afterwards it requires
state merged at the pinned head. GitLab also checks sha atomically on the merge. Update has no
such precondition at GitLab, so a head that moves between the preflight read and the PUT leaves
the outcome uncertain.
issue.create carries no guard: before a create nothing exists to compare, so the one POST is
sent as approved. The same input approved and sent again opens a second issue. GitLab requires
body.title; every other body member, such as assignee_ids or confidential, is sent as
supplied, so read the whole input before approving.
merge_request.note.create and merge_request.discussion.reply are creates too, and unguarded
for the same reason: the same input approved and sent again adds a second note. Their bodies are
closed to the note text (body, plus internal for a merge-request note). GitLab runs quick
actions written in a note, such as /close or /approve, so read the text before approving.
The text body is required and must be a JSON string, and internal a JSON boolean; a body
without its text, or with any other key such as created_at or merge_request_diff_head_sha, is
refused before any request.
merge_request.discussion.resolve takes resolved as a JSON boolean only ("true" or 1 is
refused before any request), reads the discussion first and refuses unless it is the requested
discussion and resolvable, sends exactly resolved, and requires GitLab's answer to carry the
requested discussion id and state.
These four are checked against a fixture in the pinned document's shapes, not against a running
GitLab. The merge-when-pipeline-succeeds variant of the merge and the reopen variant of the
update are merge_request.auto_merge and merge_request.reopen, each with its body member fixed.
Against a live GitLab
The 2026-10-02 replay returned HTTP 200 for all eighteen current reads and reused saved
credentials after an owner restart. Tags, releases and deployments returned empty lists;
job.trace returned text. One initial revalidation returned unavailable before a later
explicit retry succeeded, and that failure remains recorded and unexplained. This replay
performed no provider writes.
The earlier dedicated sandbox run answered the eleven reads then selected and the job trace as text; refused a merge with a stale pinned head and a merge naming the wrong pipeline before any request; merged one merge request with exactly one PUT; and refused an update of the merged request on the state check. Earlier in that run, a create opened a merge request at its pinned head, a duplicate was refused by GitLab's 409, and a create whose branch moved between the preflight read and the POST was classified uncertain.
Use saved credentials
The gitlab connector ships its declaration with the catalog provider
(adapters/catalog/providers/gitlab/connector.json): the endpoint https://gitlab.com unless an
instance names its own host, the auth profile gitlab.pat with its token header, and the
identity and scope reads. An instance of it in the CLI's configuration names only what is yours,
and the installed connectors-catalog-provider beside connectors serves it:
[connectors.gitlab]
enabled = true
[connectors.gitlab.instances.work]
endpoint = "https://gitlab.example.com"
connections connect --connector gitlab --instance work then reads the token at a hidden
prompt, runs the identity and scope reads and refuses a token below the required scope api.
The repository's catalog provider guide
has the complete commands, and the
guarded merge guide
the approval steps for a write. Validation evidence lasts 60 seconds; connections revalidate
renews it without re-entry, connections reconnect replaces the credential of a connection the
provider refused and refuses a changed identity, and connections revoke is local revocation
that does not revoke the token at GitLab.
Limits
- The bundle carries parameters, media types and response statuses, not request or response
schemas: a
bodyis passed through and validated only by GitLab. - Operations that need a header or cookie parameter, a multipart or form body, or that answer
binary content are not served; a selection naming one is refused at load or fails at request
time with a safe error. The pinned source declares
commit.create,file.updateandproject.createas multipart only; a cited media-type correction inadapters/gitlab/upstream/openapi_v3.amendments.jsonsends them as JSON, as GitLab's reference documents them. - One token-header authentication profile. OAuth is not offered.
- A guard compares scalars for equality; it cannot express ranges. Absence is expressed only as a
read after the write answering 404 (
tag.delete,branch.delete).