Gitlab
Read and write GitLab data via GraphQL (REST fallback for branches and merge accept). Projects, merge requests, issues, releases, pipelines, labels, members, branches, and a cross-project review dashboard with todos. No CLI dependencies — uses native fetch with a personal access token stored in a swamp vault.
Authentication
Requires a GitLab personal access token with api scope, stored in a
swamp vault.
Usage
# Store the token once (prompts; the value never touches shell history)
swamp vault create local_encryption gitlab # once, if you have no vault yet
swamp vault put gitlab TOKEN
# Create with vault-stored token
swamp model create @webframp/gitlab gitlab \
--global-arg host=git.example.org \
--global-arg 'token=${{ vault.get("gitlab", "TOKEN") }}'
# Read operations
swamp model method run gitlab list_projects
swamp model method run gitlab list_issues --input project=group/repo
swamp model method run gitlab list_merge_requests --input project=group/repo
# Dashboard (GraphQL)
swamp model method run gitlab list_my_merge_requests
swamp model method run gitlab list_my_merge_requests --input role=reviewer
# Write operations
swamp model method run gitlab create_issue --input project=group/repo --input title="New issue"
swamp model method run gitlab add_issue_note --input project=group/repo --input iid=1 --input body="Comment"
swamp model method run gitlab create_merge_request --input project=group/repo --input title="MR" --input sourceBranch=featureMethods
Read:
- list_projects, get_project_info, list_merge_requests, list_issues
- list_commits (REST repository/commits; optional ref + since bound)
- get_file (raw file content at a ref — a pasted blob URL, or a project/filePath/ref trio)
- get_issue (full description body, keyed by project + iid)
- list_releases, list_pipelines, list_issue_notes, list_mr_notes
- list_mr_discussions (resolvable threads + resolution state + diff position)
- list_issue_discussions (issue discussion threads; not resolvable, unlike MR discussions)
- list_labels, list_members, list_branches
- list_my_merge_requests (GraphQL — cross-project dashboard + todos)
- list_todos (GraphQL — ALL todos, paginated past the 20-cap, each with target lifecycle state)
- get_merge_request (detailed_merge_status + why an MR can/can't merge + source/target branch + webUrl)
- get_pipeline_jobs (jobs + failure_reason; defaults to failed jobs)
- get_job_log (tail of a CI job's trace)
- get_pipeline_by_iid (GraphQL — resolves a pipeline's global id from its iid)
- list_pipeline_bridges (a pipeline's trigger jobs and the downstream pipelines they fan out to)
- list_repository_tree (directory listing at a ref, paginated past GitLab's per-page cap)
- check_mr_merge_endpoint (diagnostic: OPTIONS-probes an MR's merge endpoint to distinguish a proxy 405 from GitLab's own)
- search (REST search API — code, commits, issues, MRs, notes, wiki, and more, instance-wide or within a group or project)
Write:
- create_issue, update_issue (both accept assignees, milestone, dueDate,
confidential, weight; update_issue also accepts addLabels/removeLabels for
incremental label changes alongside the wholesale
labelsreplacement) - add_issue_note (top-level or, via discussionId, a threaded reply)
- update_issue_note / delete_issue_note (edit or remove an issue comment by id)
- set_issue_assignees (assign/reassign/unassign by username; CE keeps one, EE many)
- remove_work_item_parent (GraphQL — detach a work item from its parent)
- convert_work_item_type (GraphQL — change a work item's type, e.g. Task to Issue)
- unassign_from_issues (fan-out: remove an assignee from many issues in a project)
- create_merge_request, update_merge_request, merge, add_mr_note
- update_mr_note / delete_mr_note (edit or remove an MR comment by id)
- set_mr_assignees (assign/reassign/unassign by username; CE keeps one, EE many)
- unassign_from_mrs (fan-out: remove an assignee from many MRs in a project)
- set_mr_reviewers / remove_mr_reviewers (replace or fan-out-remove reviewers by username)
- resolve_mr_discussion (resolve/unresolve an MR discussion thread; mutating)
- create_label
- mark_todo_done (clear a handled to-do off the pending queue)
- mark_todos_done (bulk-clear many todos in one call, sequential; companion to mark_todo_done)
- rebase_merge_request (rebase a stale MR onto its target, polling to completion)
- retry_job / retry_pipeline (re-run CI after a transient failure)
- cancel_pipeline (stop a pipeline that is still running)
- create_pipeline_schedule (CI/CD schedule, plus per-variable sub-resource calls)
- play_pipeline_schedule (trigger a fresh pipeline carrying a schedule's variables)
- create_branch, commit_file (create-or-update a single file via the commits API)
- update_project_visibility
- create_snippet / list_snippets / get_snippet / update_snippet / delete_snippet (instance and project snippets, multi-file)
2026.10.21.3
Fixed: The README told you to store the token with a vault set subcommand
that swamp does not have, passing the token on the command line where it would
land in shell history. It now creates the vault if needed and stores the token
with swamp vault put gitlab TOKEN, which prompts for the value. The registry
Quick Start shows the same steps.
Changed: The token global argument description now says to supply it as
${{ vault.get("gitlab", "TOKEN") }} and never as a literal.
Upgrade note: No schema or behavioral changes. The model version moves to 2026.10.21.3 with an identity upgrade.
Global Arguments
| Argument | Type | Description |
|---|---|---|
| host | string | GitLab hostname (e.g. git.example.org) |
| token | string | GitLab personal access token with api scope. Supply it as ${{ vault.get("gitlab", "TOKEN") }}; never pass the literal token |
| Argument | Type | Description |
|---|---|---|
| project | string | Project path (e.g. mygroup/myproject) |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| state | enum |
| Argument | Type | Description |
|---|---|---|
| project | string | Project path (group/repo) or numeric ID |
| ref | string | Branch or ref to list from; empty uses the default branch |
| since | string | Only commits after this ISO 8601 timestamp; empty = no lower bound |
| perPage | number | Page size (max 100) |
| Argument | Type | Description |
|---|---|---|
| url? | string | GitLab blob URL, e.g. https://<host>/<group>/<project>/-/blob/<ref>/<path>. |
| project? | string | Project path. Alternative to url — requires filePath. |
| filePath? | string | File path within the repository. Alternative to url — requires project. |
| ref? | string | Branch, tag, or commit. Only used with project/filePath; |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| state | enum |
| Argument | Type | Description |
|---|---|---|
| project | string |
| Argument | Type | Description |
|---|---|---|
| project | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| title | string | |
| description | string | |
| labels | array | |
| assignees? | array | Usernames to assign on creation |
| dueDate? | string | Due date, ISO 8601 (YYYY-MM-DD) |
| confidential? | boolean | |
| weight? | number | Issue weight (GitLab EE) |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| title? | string | |
| description? | string | |
| labels? | array | |
| addLabels? | array | Labels to add incrementally, alongside any wholesale `labels` replacement |
| removeLabels? | array | Labels to remove incrementally, alongside any wholesale `labels` replacement |
| stateEvent? | enum | |
| assignees? | array | Replace (wholesale) the issue's assignees by username |
| dueDate? | string | Due date, ISO 8601 (YYYY-MM-DD) |
| confidential? | boolean | |
| weight? | number | Issue weight (GitLab EE) |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| body | string | Comment body (GitLab Flavored Markdown). @mentions notify the user. If the body starts with @, escape it as \\@ on the CLI so `--arg` does not treat it as a file path; a mid-body @ needs no escape. |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| noteId | number | |
| body | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| noteId | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| usernames | array |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| type | string | Target type name, case-insensitive (e.g. Issue, Task, Incident) |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iids | array | |
| username? | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| first | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number |
| Argument | Type | Description |
|---|---|---|
| todoId | string | Todo ID — the gid (gid://gitlab/Todo/NNN) or the numeric id |
| Argument | Type | Description |
|---|---|---|
| state | enum | Which todos to fetch (default pending) |
| maxTodos | number | Safety cap on total todos fetched across pages |
| Argument | Type | Description |
|---|---|---|
| todoIds | array | Todo ids (gid://gitlab/Todo/NNN or numeric) |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| title | string | |
| sourceBranch | string | |
| targetBranch | string | |
| description | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| skipCi | boolean |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| pipelineId | number | |
| scope | enum |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| jobId | number | |
| tailLines | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| jobId | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| pipelineId | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| pipelineId | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| pipelineId | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| description | string | |
| cron | string | |
| ref | string | |
| cronTimezone | string | |
| active | boolean | |
| variables | array |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| scheduleId | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| squash | boolean |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| title? | string | |
| description? | string | |
| labels? | array | |
| stateEvent? | enum |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| body | string | Comment body (GitLab Flavored Markdown). @mentions notify the user. If the body starts with @, escape it as \\@ on the CLI so `--arg` does not treat it as a file path; a mid-body @ needs no escape. |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| noteId | number | |
| body | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| noteId | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| usernames | array |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iids | array | |
| username? | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iids | array | |
| username? | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| usernames | array |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| first | number |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number | |
| discussionId | string | |
| resolved | boolean |
| Argument | Type | Description |
|---|---|---|
| project | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| name | string | |
| color | string | |
| description | string |
| Argument | Type | Description |
|---|---|---|
| project | string |
| Argument | Type | Description |
|---|---|---|
| project | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| name | string | |
| ref | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| branch | string | |
| filePath | string | |
| content | string | |
| commitMessage | string |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| path | string | |
| ref | string |
| Argument | Type | Description |
|---|---|---|
| query | string | Search term (GitLab requires at least 2 characters) |
| project | string | Limit to a project path (e.g. group/repo); empty = not limited |
| group | string | Limit to a group path; empty = not limited. Mutually exclusive with project |
| ref | string | Branch or tag for blobs/commits/wiki_blobs; requires project |
| state | enum | State filter for issues/merge_requests scopes; empty = any state |
| limit | number | Maximum results to return |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| visibility | enum |
| Argument | Type | Description |
|---|---|---|
| project | string | |
| iid | number |
| Argument | Type | Description |
|---|---|---|
| title | string | |
| files | array | Snippet files. Mapped to GitLab's files[] with file_path/content. |
| description | string | |
| project? | string | Project path. Omit for a personal/instance snippet. |
| Argument | Type | Description |
|---|---|---|
| project? | string | When given, list this project's snippets instead of personal ones |
| Argument | Type | Description |
|---|---|---|
| id | number | |
| project? | string | Project path, for a project snippet. Omit for an instance/personal snippet. |
| includeContent | boolean | Fetch raw file content in addition to metadata |
| filePath? | string | When given (implies includeContent), fetch this file's raw |
| Argument | Type | Description |
|---|---|---|
| id | number | |
| project? | string | Project path, for a project snippet. Omit for an instance/personal snippet. |
| title? | string | |
| description? | string | |
| files? | array | Replacement files for the snippet. Mapped to GitLab's |
| Argument | Type | Description |
|---|---|---|
| id | number | |
| project? | string | Project path, for a project snippet. Omit for an instance/personal snippet. |
Resources
Prioritized action items dashboard from cross-project MR data
2026.10.21.2
Changed: The remaining numeric ID arguments now reject a value that is not a
positive integer, such as 0, -3 or 1.5, at input validation, before any
request reaches GitLab. Previously these values were sent to GitLab as-is, in
REST paths such as /jobs/1.5 or GraphQL ids such as gid://gitlab/Note/1.5.
Affected arguments:
noteIdonupdate_issue_note,delete_issue_note,update_mr_noteanddelete_mr_notepipelineIdonget_pipeline_jobs,retry_pipeline,cancel_pipelineandlist_pipeline_bridgesjobIdonget_job_logandretry_jobscheduleIdonplay_pipeline_scheduleidonget_snippet,update_snippetanddelete_snippet
Changed: get_job_log tailLines must be an integer from 1 to 10000.
Before, 0 or a negative value was silently raised to 1, and any size was
accepted. The default stays 200.
Changed: milestone on create_issue rejects 0, a string of only zeros
("0"), and any number that is not a positive integer. These used to be sent to
GitLab as-is; milestone ID 0 names no milestone. On update_issue, 0 and
"0" still clear the issue's milestone, and other non-integer or negative
numbers are rejected. On both methods an empty string is now rejected at
validation; before, it was looked up as a title and failed with "no milestone
titled". Titles are unaffected, including ones that look like numbers, such as
"1.5".
Upgrade note: No globalArguments change. Resource schemas are unchanged.
2026.10.21.1
Added: Two methods, remove_work_item_parent and
convert_work_item_type. They use the work-item GraphQL API
(workItemUpdate with the hierarchy widget, and workItemConvert), which
the REST issues API does not cover.
remove_work_item_parentleaves a work item with no ancestor. It fails unless the mutation response confirms the parent is gone. If the token cannot read the parent, it fails before sending the mutation, since GitLab would refuse it. Requires GitLab 17.2 or later.convert_work_item_typechanges a work item's type. Converting a Task to an Issue makes it show on issue boards. Type names match the project's work item types, ignoring case and surrounding whitespace. An unknown name fails with the list of available types. It fails if GitLab returns a type other than the one requested.
Neither method sends a mutation when the work item is already in the
requested state. Results land in the new workItemParent and
workItemType resources, under the data names
<project>-work-item-<iid>-parent and -type, with / in the project
path written as ~ (for example group~proj-work-item-7-parent). No globalArguments change.
Contributed by @zWaR (#500).
Changed: Every method that takes an iid or iids argument (27
methods) now rejects a value that is not a positive integer, such as 0,
-3 or 1.5, at input validation, before any request reaches GitLab.
Previously these values were sent to GitLab as-is. For unassign_from_issues,
unassign_from_mrs and remove_mr_reviewers, one invalid entry in iids now
rejects the whole call. Before, the valid entries were processed and the
invalid one was recorded under failed. Resource schemas are unchanged.
2026.10.14.2
Added: search method wrapping the GitLab REST search API at instance,
group, or project level (/search, /groups/:id/search,
/projects/:id/search). Supports every REST scope (blobs, commits, issues,
merge_requests, milestones, notes, projects, snippet_titles, users,
wiki_blobs), plus ref (project only) and state (issues/merge_requests
only). Results land in the new searchResults resource, follow
x-next-page up to limit (max 1000), and set truncated when more exist.
Invalid combinations fail before any request: ref outside
blobs/commits/wiki_blobs, state=merged outside merge_requests, and queries
under 2 characters. No globalArguments change.
2026.10.14.1
Added: The manifest now declares license: Apache-2.0 (SPDX). The registry quality rubric scores a declared license separately from the bundled LICENSE.md.
Upgrade note: Metadata only. No code, schema, or behavioral changes.
2026.10.07.1
Fixed: get_pipeline_by_iid failed with Field 'webUrl' doesn't exist on type 'Pipeline' on GitLab instances whose GraphQL schema lacks that field.
Closes #476.
Changed: The query no longer requests webUrl. The webUrl in the
pipelineByIid resource is now built as
https://<host>/<project>/-/pipelines/<id>. No schema or globalArguments
change.
Modified 1 behaviors
2026.10.14.2
Added: search method wrapping the GitLab REST search API at instance,
group, or project level (/search, /groups/:id/search,
/projects/:id/search). Supports every REST scope (blobs, commits, issues,
merge_requests, milestones, notes, projects, snippet_titles, users,
wiki_blobs), plus ref (project only) and state (issues/merge_requests
only). Results land in the new searchResults resource, follow
x-next-page up to limit (max 1000), and set truncated when more exist.
Invalid combinations fail before any request: ref outside
blobs/commits/wiki_blobs, state=merged outside merge_requests, and queries
under 2 characters. No globalArguments change.
2026.10.14.1
Added: The manifest now declares license: Apache-2.0 (SPDX). The registry quality rubric scores a declared license separately from the bundled LICENSE.md.
Upgrade note: Metadata only. No code, schema, or behavioral changes.
2026.10.07.1
Fixed: get_pipeline_by_iid failed with Field 'webUrl' doesn't exist on type 'Pipeline' on GitLab instances whose GraphQL schema lacks that field.
Closes #476.
Changed: The query no longer requests webUrl. The webUrl in the
pipelineByIid resource is now built as
https://<host>/<project>/-/pipelines/<id>. No schema or globalArguments
change.
Modified 1 behaviors. updated labels
2026.10.14.1
Added: The manifest now declares license: Apache-2.0 (SPDX). The registry quality rubric scores a declared license separately from the bundled LICENSE.md.
Upgrade note: Metadata only. No code, schema, or behavioral changes.
2026.10.07.1
Fixed: get_pipeline_by_iid failed with Field 'webUrl' doesn't exist on type 'Pipeline' on GitLab instances whose GraphQL schema lacks that field.
Closes #476.
Changed: The query no longer requests webUrl. The webUrl in the
pipelineByIid resource is now built as
https://<host>/<project>/-/pipelines/<id>. No schema or globalArguments
change.
2026.10.07.1
Fixed: get_pipeline_by_iid failed with Field 'webUrl' doesn't exist on type 'Pipeline' on GitLab instances whose GraphQL schema lacks that field.
Closes #476.
Changed: The query no longer requests webUrl. The webUrl in the
pipelineByIid resource is now built as
https://<host>/<project>/-/pipelines/<id>. No schema or globalArguments
change.
2026.10.01.1
Fixed: create_issue failed on every call against GitLab CE (and any
instance without weight on CreateIssueInput). The mutation always declared
$assigneeUsernames and $weight, but CreateIssueInput has no
assigneeUsernames field and weight is EE-only. Closes #464.
Changed: create_issue now declares and sends only the optional fields you
supply (description, labels, milestone, dueDate, confidential,
weight). assignees are applied with a second issueSetAssignees call after
the issue is created, then verified as before. If assigning fails, the error
names the already-created issue. weight still requires GitLab EE when passed.
No schema or globalArguments change.
2026.09.23.1
Changed: Documented @-mention escaping on add_issue_note and
add_mr_note. GitLab notifies a user only when the comment body contains a
real @handle mention, but the swamp CLI's --arg body=@... reads a value
that starts with @ as a file path — so --arg body=@ADADY ... fails with
"input file not found" rather than posting a mention. The method descriptions
and the body argument's schema description now state the fix: escape a
leading @ as \@ on the CLI (e.g. --arg 'body=\@ADADY please review'); a
mid-body @ needs no escape. Documentation only — no schema, resource, or
globalArguments change, and no change to how a body is posted once it reaches
the method.
2026.09.21.1
Added: Snippet management, closing GitHub issue #411:
create_snippet— create an instance/personal snippet (POST /snippets) or a project snippet (POST /projects/:id/snippets) depending on whether aprojectargument is given. Accepts one or morefiles(filePath+content, mapped to GitLab'sfile_path/content) — the legacy top-levelfile_name+contentform is not supported.visibilitydefaults toprivate(private/internal/public).list_snippets— the caller's personal snippets (GET /snippets), or a project's snippets whenprojectis given (GET /projects/:id/snippets). Requestsper_page=100(matchinglist_branches/list_repository_tree) and flagstruncatedfrom GitLab'sx-next-pageheader.get_snippet— fetch a snippet's metadata (instance/personal or project, byid). SetincludeContentto also fetch raw file content — the whole snippet by default, or a specific file viafilePath— capped at 500KB and redacted for common credential patterns exactly likeget_file, including rejecting binary content rather than decoding it as corrupted text.update_snippet— update an existing snippet'stitle,description,visibility, and/orfiles(only the fields provided are sent). Throws if called with none of those fields, rather than sending an empty PUT.delete_snippet— delete a snippet byid(instance/personal, or project whenprojectis given). Throws a descriptive error on a non-2xx response (e.g. 404) rather than silently succeeding.
All five accept an optional project argument: omitted means the instance
(personal) snippet endpoints, provided means the project-scoped ones. New
resources: snippetDetail (create/get/update — infinite lifetime, snippets
are durable), snippetList (list — 15m lifetime), snippetDeleted (delete
— infinite lifetime).
Changed: None — this release is purely additive. GitLabClient gained
new instance-level HTTP helpers (getInstance, getInstanceList,
postInstance, putInstance, deleteInstance, getRawTextOrNull) plus
project-scoped get/del, since GitLab's snippet GraphQL surface doesn't
cover create/delete and instance snippets live directly off the API base URL
rather than under /projects/:id like everything else this model reads.
getRawTextOrNull refuses to send the configured PRIVATE-TOKEN to any host
other than this client's own GitLab instance, since raw_url is a value
GitLab's API hands back rather than a code-controlled path.
Upgrade note: No co-upgrades required. All new resources and methods are
additive; no globalArguments change.
2026.09.23.1
Changed: Documented @-mention escaping on add_issue_note and
add_mr_note. GitLab notifies a user only when the comment body contains a
real @handle mention, but the swamp CLI's --arg body=@... reads a value
that starts with @ as a file path — so --arg body=@ADADY ... fails with
"input file not found" rather than posting a mention. The method descriptions
and the body argument's schema description now state the fix: escape a
leading @ as \@ on the CLI (e.g. --arg 'body=\@ADADY please review'); a
mid-body @ needs no escape. Documentation only — no schema, resource, or
globalArguments change, and no change to how a body is posted once it reaches
the method.
2026.09.21.1
Added: Snippet management, closing GitHub issue #411:
create_snippet— create an instance/personal snippet (POST /snippets) or a project snippet (POST /projects/:id/snippets) depending on whether aprojectargument is given. Accepts one or morefiles(filePath+content, mapped to GitLab'sfile_path/content) — the legacy top-levelfile_name+contentform is not supported.visibilitydefaults toprivate(private/internal/public).list_snippets— the caller's personal snippets (GET /snippets), or a project's snippets whenprojectis given (GET /projects/:id/snippets). Requestsper_page=100(matchinglist_branches/list_repository_tree) and flagstruncatedfrom GitLab'sx-next-pageheader.get_snippet— fetch a snippet's metadata (instance/personal or project, byid). SetincludeContentto also fetch raw file content — the whole snippet by default, or a specific file viafilePath— capped at 500KB and redacted for common credential patterns exactly likeget_file, including rejecting binary content rather than decoding it as corrupted text.update_snippet— update an existing snippet'stitle,description,visibility, and/orfiles(only the fields provided are sent). Throws if called with none of those fields, rather than sending an empty PUT.delete_snippet— delete a snippet byid(instance/personal, or project whenprojectis given). Throws a descriptive error on a non-2xx response (e.g. 404) rather than silently succeeding.
All five accept an optional project argument: omitted means the instance
(personal) snippet endpoints, provided means the project-scoped ones. New
resources: snippetDetail (create/get/update — infinite lifetime, snippets
are durable), snippetList (list — 15m lifetime), snippetDeleted (delete
— infinite lifetime).
Changed: None — this release is purely additive. GitLabClient gained
new instance-level HTTP helpers (getInstance, getInstanceList,
postInstance, putInstance, deleteInstance, getRawTextOrNull) plus
project-scoped get/del, since GitLab's snippet GraphQL surface doesn't
cover create/delete and instance snippets live directly off the API base URL
rather than under /projects/:id like everything else this model reads.
getRawTextOrNull refuses to send the configured PRIVATE-TOKEN to any host
other than this client's own GitLab instance, since raw_url is a value
GitLab's API hands back rather than a code-controlled path.
Upgrade note: No co-upgrades required. All new resources and methods are
additive; no globalArguments change.
2026.09.21.1
Added: Snippet management, closing GitHub issue #411:
create_snippet— create an instance/personal snippet (POST /snippets) or a project snippet (POST /projects/:id/snippets) depending on whether aprojectargument is given. Accepts one or morefiles(filePath+content, mapped to GitLab'sfile_path/content) — the legacy top-levelfile_name+contentform is not supported.visibilitydefaults toprivate(private/internal/public).list_snippets— the caller's personal snippets (GET /snippets), or a project's snippets whenprojectis given (GET /projects/:id/snippets). Requestsper_page=100(matchinglist_branches/list_repository_tree) and flagstruncatedfrom GitLab'sx-next-pageheader.get_snippet— fetch a snippet's metadata (instance/personal or project, byid). SetincludeContentto also fetch raw file content — the whole snippet by default, or a specific file viafilePath— capped at 500KB and redacted for common credential patterns exactly likeget_file, including rejecting binary content rather than decoding it as corrupted text.update_snippet— update an existing snippet'stitle,description,visibility, and/orfiles(only the fields provided are sent). Throws if called with none of those fields, rather than sending an empty PUT.delete_snippet— delete a snippet byid(instance/personal, or project whenprojectis given). Throws a descriptive error on a non-2xx response (e.g. 404) rather than silently succeeding.
All five accept an optional project argument: omitted means the instance
(personal) snippet endpoints, provided means the project-scoped ones. New
resources: snippetDetail (create/get/update — infinite lifetime, snippets
are durable), snippetList (list — 15m lifetime), snippetDeleted (delete
— infinite lifetime).
Changed: None — this release is purely additive. GitLabClient gained
new instance-level HTTP helpers (getInstance, getInstanceList,
postInstance, putInstance, deleteInstance, getRawTextOrNull) plus
project-scoped get/del, since GitLab's snippet GraphQL surface doesn't
cover create/delete and instance snippets live directly off the API base URL
rather than under /projects/:id like everything else this model reads.
getRawTextOrNull refuses to send the configured PRIVATE-TOKEN to any host
other than this client's own GitLab instance, since raw_url is a value
GitLab's API hands back rather than a code-controlled path.
Upgrade note: No co-upgrades required. All new resources and methods are
additive; no globalArguments change.
Modified 1 behaviors
2026.09.20.2
Added: Issue management brought to parity with merge requests, closing GitHub issue #412:
set_issue_assignees— set (replace) an issue's assignees by username; pass an empty list to unassign. Mirrorsset_mr_assignees: GitLab CE keeps one assignee, EE/Premium support multiple, and the method throws loudly if GitLab silently drops a requested username.unassign_from_issues— remove an assignee (default: the authenticated user) from multiple issues in a project in one fan-out. Mirrorsunassign_from_mrs: usesoperationMode: REMOVEso co-assignees are preserved, is idempotent, and isolates per-issue failures into afailedarray without aborting the batch.update_issue_note/delete_issue_note— edit or remove an issue comment by note id. Mirrorsupdate_mr_note/delete_mr_note, including the "note not found or permission denied" error on GitLab's null-payload path.list_issue_discussions— list discussion threads on an issue, with the same hoisted resolution/location/author fields and slim diff position aslist_mr_discussions. GitLab does not support resolving plain issue discussions (only MR/diff discussions), so there is noresolve_issue_discussionmethod —resolvable/resolvedsimply readfalsefor issue threads.create_issueandupdate_issuenow accept optionalassignees(usernames),milestone(numeric id or a title resolved via the GitLab API),dueDate,confidential, andweight(GitLab EE).update_issueadditionally acceptsaddLabels/removeLabelsfor incremental label changes alongside the existing wholesalelabelsreplacement, so callers can adjust labels without a read-modify-write race.add_issue_notenow accepts an optionaldiscussionIdto reply into an existing thread (fromlist_issue_discussions), mirroringadd_mr_note.
Changed: None of the above are breaking — every new argument is optional
and every new method is additive. Existing calls to create_issue,
update_issue, and add_issue_note behave exactly as before when the new
arguments are omitted.
Upgrade note: No co-upgrades required. New resources issueAssignees,
issueUnassignResult, and issueDiscussions are additive; no
globalArguments change.
Added 1, removed 1 behaviors
2026.09.20.1
Added: Ten new methods closing gaps reported in issue #420:
cancel_pipeline— stop a pipeline that is still running (retry_pipelineonly restarts one that's already finished).get_pipeline_by_iid— resolve a pipeline's global id from its project-scoped iid via GraphQL.get_pipeline_jobsandretry_pipelineneed the global id;list_pipelinesand the web UI only show the iid.list_pipeline_bridges— list a pipeline's trigger jobs and the downstream (child) pipelines they fan out to, whichget_pipeline_jobscannot see.create_pipeline_schedule— create a CI/CD pipeline schedule, including its variables sub-resource.play_pipeline_schedule— trigger a fresh pipeline carrying a schedule's CI/CD variables (pair withget_pipeline_by_iidto find the resulting pipeline, since GitLab returns 202 Accepted with no pipeline id).create_branch— create a branch (list_brancheswas read-only).commit_file— create or update a single file via a commit; falls back from a create action to an update action automatically when the file already exists.list_repository_tree— list a repository directory at a ref, following GitLab'sx-next-pageheader across pages (capped at 20 pages) so a large directory is not silently cut off.update_project_visibility— change a project's visibility (get_project_infoonly reads it).check_mr_merge_endpoint— diagnostic: sends a non-mutating OPTIONS probe to an MR's merge endpoint and reports back the real Allow/Server/Via headers, to distinguish a proxy's 405 from GitLab's own.
Changed: get_file now also accepts a project/filePath/ref input
trio as an alternative to the existing blob url input — for callers that
already have those three values and shouldn't have to assemble a blob URL
just to have it parsed apart again. The existing size cap, credential
redaction, and binary-file rejection apply to both input paths. fileContent
output schema is unchanged.
Upgrade note: All additive — no globalArguments changes, no breaking
schema changes. Ten new resources were added (pipelineCancel,
pipelineByIid, pipelineBridges, pipelineSchedule,
pipelineSchedulePlayResult, branchCreateResult, commitFileResult,
repositoryTree, projectVisibility, mrMergeEndpointCheck).
Modified 1 behaviors
2026.09.18.1
Upgrade note: Normalized npm:zod dependency version to 4.6.5 across the
repo. No behavioral changes in this extension.
2026.09.15.1
Changed: Bump zod 4.4.3 → 4.6.5
2026.09.08.2
Added: get_file method — fetches a file's raw content at a given ref
via the REST repository/files/:path/raw endpoint. Accepts a GitLab blob URL
(e.g. https://<host>/<group>/<project>/-/blob/<ref>/<path>, the kind you'd
paste from the web UI) and resolves project, ref, and path from it. A
ref containing slashes (e.g. feat/my-feature) is disambiguated by probing
the API for each plausible ref/path split, rather than guessing the first
one. The URL's host must match the model instance's configured host,
tolerating an explicit default port (:443) — a URL for a different GitLab
instance is rejected. If more than one ref/path split resolves (e.g. a
branch and a tag sharing a slash-containing name), a warning is logged and
the shortest-ref candidate is used; probing is bounded to 10 splits so a
pathologically deep path can't force an unbounded burst of API calls, and
a non-404 error on one candidate (e.g. GitLab rejecting a malformed ref)
no longer aborts the whole call — later candidates still get a chance to
resolve. Writes a new fileContent resource,
with a collision-resistant instance name (hashed project/ref/path) so
distinct files with hyphenated components can't overwrite each other.
Only text/code files are supported — binary content (images, archives,
compiled artifacts) is detected by sniffing for a NUL byte, the same
heuristic Git itself uses, rather than trusting Content-Type alone
(GitLab's raw-file endpoint commonly serves extension-less text files
like Dockerfile or Jenkinsfile as application/octet-stream). If a
resolved candidate turns out to be binary while a different ref/path
split resolves as text, that candidate is skipped and a warning notes it
so the mismatch isn't silent — and if every candidate is either binary or
missing, the binary rejection is what surfaces (it means the target file
was found, just of an unsupported type), not a less useful later error.
The response body is read up to the size cap and the rest of the stream
is discarded, so an oversized file (a multi-GB vendored bundle, a SQL
dump) is never fully buffered into memory before being trimmed down.
Content is capped at 500KB measured in bytes (not JS string length),
truncating on a UTF-8 codepoint boundary so a multi-byte character
straddling the cap isn't corrupted into a replacement character, and
common credential patterns are redacted, same as get_job_log's trace
handling. truncated reflects whether the network read itself was cut
short, not just whether the post-redaction content still exceeds the
cap — a large file with a credential near the front can shrink under
500KB once redacted, but real data was still dropped upstream, so the
flag stays honest instead of silently reporting a complete file. If the
network read cap itself lands mid-character, the dangling lead byte at
the tail is trimmed rather than decoded into a replacement character
(U+FFFD) — this is checked independently of the final 500KB trim, since
redaction can shrink content enough to skip that second trim entirely.
That trim only applies when the read was actually cut short; a complete
file's genuine trailing bytes (e.g. Latin-1 text ending in a byte that
happens to match a UTF-8 lead-byte pattern) are left alone rather than
guessed at. A malformed percent-escape in the URL (e.g. a real filename
like 100%complete.md) now surfaces as a descriptive error naming the
URL instead of a bare URIError.
Upgrade note: no schema or globalArguments change for existing resources.
Running any method on an existing instance migrates it to 2026.09.08.2 as a
no-op.
2026.09.08.1
Fixed: the manifest description's method list (published to the registry
and README) was missing seven methods that already shipped in code:
list_commits, get_issue, list_mr_discussions, resolve_mr_discussion,
set_mr_reviewers, remove_mr_reviewers, and unassign_from_mrs. Users
browsing the registry entry had no way to discover these without reading
source. No behavioral change — documentation only.
Upgrade note: no schema or globalArguments change. Running any method on
an existing instance migrates it to 2026.09.08.1 as a no-op.
2026.09.02.1
Added: get_issue method — fetches a single issue by project and iid,
including its description body, via GraphQL project.issue(iid). Writes the
existing issueDetail resource (the same shape produced by create_issue and
update_issue). Enables reading full work-item details, such as a directly
addressed tier-1 to-do, without creating or mutating the issue.
Fixed: the 2026.07.30.1 upgrade erroneously injected sourceBranch,
targetBranch, and webUrl into globalArguments (only host and token
are valid), which broke every method on upgraded instances with an
Unknown argument(s) validation error. That upgrade is now a no-op on global
arguments (the mergeStatus fields i
2026.09.08.2
Added: get_file method — fetches a file's raw content at a given ref
via the REST repository/files/:path/raw endpoint. Accepts a GitLab blob URL
(e.g. https://<host>/<group>/<project>/-/blob/<ref>/<path>, the kind you'd
paste from the web UI) and resolves project, ref, and path from it. A
ref containing slashes (e.g. feat/my-feature) is disambiguated by probing
the API for each plausible ref/path split, rather than guessing the first
one. The URL's host must match the model instance's configured host,
tolerating an explicit default port (:443) — a URL for a different GitLab
instance is rejected. If more than one ref/path split resolves (e.g. a
branch and a tag sharing a slash-containing name), a warning is logged and
the shortest-ref candidate is used; probing is bounded to 10 splits so a
pathologically deep path can't force an unbounded burst of API calls, and
a non-404 error on one candidate (e.g. GitLab rejecting a malformed ref)
no longer aborts the whole call — later candidates still get a chance to
resolve. Writes a new fileContent resource,
with a collision-resistant instance name (hashed project/ref/path) so
distinct files with hyphenated components can't overwrite each other.
Only text/code files are supported — binary content (images, archives,
compiled artifacts) is detected by sniffing for a NUL byte, the same
heuristic Git itself uses, rather than trusting Content-Type alone
(GitLab's raw-file endpoint commonly serves extension-less text files
like Dockerfile or Jenkinsfile as application/octet-stream). If a
resolved candidate turns out to be binary while a different ref/path
split resolves as text, that candidate is skipped and a warning notes it
so the mismatch isn't silent — and if every candidate is either binary or
missing, the binary rejection is what surfaces (it means the target file
was found, just of an unsupported type), not a less useful later error.
The response body is read up to the size cap and the rest of the stream
is discarded, so an oversized file (a multi-GB vendored bundle, a SQL
dump) is never fully buffered into memory before being trimmed down.
Content is capped at 500KB measured in bytes (not JS string length),
truncating on a UTF-8 codepoint boundary so a multi-byte character
straddling the cap isn't corrupted into a replacement character, and
common credential patterns are redacted, same as get_job_log's trace
handling. truncated reflects whether the network read itself was cut
short, not just whether the post-redaction content still exceeds the
cap — a large file with a credential near the front can shrink under
500KB once redacted, but real data was still dropped upstream, so the
flag stays honest instead of silently reporting a complete file. If the
network read cap itself lands mid-character, the dangling lead byte at
the tail is trimmed rather than decoded into a replacement character
(U+FFFD) — this is checked independently of the final 500KB trim, since
redaction can shrink content enough to skip that second trim entirely.
That trim only applies when the read was actually cut short; a complete
file's genuine trailing bytes (e.g. Latin-1 text ending in a byte that
happens to match a UTF-8 lead-byte pattern) are left alone rather than
guessed at. A malformed percent-escape in the URL (e.g. a real filename
like 100%complete.md) now surfaces as a descriptive error naming the
URL instead of a bare URIError.
Upgrade note: no schema or globalArguments change for existing resources.
Running any method on an existing instance migrates it to 2026.09.08.2 as a
no-op.
2026.09.08.1
Fixed: the manifest description's method list (published to the registry
and README) was missing seven methods that already shipped in code:
list_commits, get_issue, list_mr_discussions, resolve_mr_discussion,
set_mr_reviewers, remove_mr_reviewers, and unassign_from_mrs. Users
browsing the registry entry had no way to discover these without reading
source. No behavioral change — documentation only.
Upgrade note: no schema or globalArguments change. Running any method on
an existing instance migrates it to 2026.09.08.1 as a no-op.
2026.09.02.1
Added: get_issue method — fetches a single issue by project and iid,
including its description body, via GraphQL project.issue(iid). Writes the
existing issueDetail resource (the same shape produced by create_issue and
update_issue). Enables reading full work-item details, such as a directly
addressed tier-1 to-do, without creating or mutating the issue.
Fixed: the 2026.07.30.1 upgrade erroneously injected sourceBranch,
targetBranch, and webUrl into globalArguments (only host and token
are valid), which broke every method on upgraded instances with an
Unknown argument(s) validation error. That upgrade is now a no-op on global
arguments (the mergeStatus fields it described belong to a resource schema,
which needs n
Modified 1 behaviors
2026.09.08.1
Fixed: the manifest description's method list (published to the registry
and README) was missing seven methods that already shipped in code:
list_commits, get_issue, list_mr_discussions, resolve_mr_discussion,
set_mr_reviewers, remove_mr_reviewers, and unassign_from_mrs. Users
browsing the registry entry had no way to discover these without reading
source. No behavioral change — documentation only.
Upgrade note: no schema or globalArguments change. Running any method on
an existing instance migrates it to 2026.09.08.1 as a no-op.
2026.09.02.1
Added: get_issue method — fetches a single issue by project and iid,
including its description body, via GraphQL project.issue(iid). Writes the
existing issueDetail resource (the same shape produced by create_issue and
update_issue). Enables reading full work-item details, such as a directly
addressed tier-1 to-do, without creating or mutating the issue.
Fixed: the 2026.07.30.1 upgrade erroneously injected sourceBranch,
targetBranch, and webUrl into globalArguments (only host and token
are valid), which broke every method on upgraded instances with an
Unknown argument(s) validation error. That upgrade is now a no-op on global
arguments (the mergeStatus fields it described belong to a resource schema,
which needs no attribute migration), and the 2026.09.02.1 upgrade removes the
stray keys from any instance already poisoned by it.
Upgrade note: running any method on an existing instance migrates it to
2026.09.02.1 and strips the stray global-argument keys automatically. No
manual intervention is required.
2026.09.01.1
Added: mergedAt field on merge requests (GraphQL mergedAt, REST
merged_at) — the timestamp an MR was merged, or null if not merged.
Added: approvers field on merge requests — the usernames who approved
(reviewed) the MR, from GraphQL approvedBy. Enables cross-boundary review
attribution (an approver helps the MR author). REST-mapped MRs default to an
empty array.
Added: list_commits method — lists a project's commits (optionally a
branch, with a since lower time bound) via the REST repository/commits
endpoint, writing a commits resource. Enables commit-based contribution
analysis (who commits to another crew's repository).
Together these support downstream review-outcome / unblock-rate scoring and cross-boundary contribution measurement.
Upgrade note: Purely additive and backward-compatible. New MR fields are
nullable / defaulted, so merge-request data stored by earlier versions still
validates on read. list_commits is a new method; no existing method changed.
2026.08.28.1
Changed: Normalized the extension license to Apache-2.0 and corrected the copyright holder to "Sean Escriva". Extensions that previously shipped an MIT LICENSE.md are now Apache-2.0, consistent with the repository root and every other extension. No code or behavioral changes.
Upgrade note: License text only. No API, schema, or runtime behavior changed.
2026.08.26.3
Fixed: Restored inline npm:zod@4.4.3 import specifiers so the registry
quality scorer can resolve dependencies and score the extension. An earlier
release used a bare "zod" import-map specifier, which published but scored as
unscored.
Changed: Retained explicit compilerOptions.strict in deno.json. No
behavioral or schema changes.
2026.09.02.1
Added: get_issue method — fetches a single issue by project and iid,
including its description body, via GraphQL project.issue(iid). Writes the
existing issueDetail resource (the same shape produced by create_issue and
update_issue). Enables reading full work-item details, such as a directly
addressed tier-1 to-do, without creating or mutating the issue.
Fixed: the 2026.07.30.1 upgrade erroneously injected sourceBranch,
targetBranch, and webUrl into globalArguments (only host and token
are valid), which broke every method on upgraded instances with an
Unknown argument(s) validation error. That upgrade is now a no-op on global
arguments (the mergeStatus fields it described belong to a resource schema,
which needs no attribute migration), and the 2026.09.02.1 upgrade removes the
stray keys from any instance already poisoned by it.
Upgrade note: running any method on an existing instance migrates it to
2026.09.02.1 and strips the stray global-argument keys automatically. No
manual intervention is required.
2026.09.01.1
Added: mergedAt field on merge requests (GraphQL mergedAt, REST
merged_at) — the timestamp an MR was merged, or null if not merged.
Added: approvers field on merge requests — the usernames who approved
(reviewed) the MR, from GraphQL approvedBy. Enables cross-boundary review
attribution (an approver helps the MR author). REST-mapped MRs default to an
empty array.
Added: list_commits method — lists a project's commits (optionally a
branch, with a since lower time bound) via the REST repository/commits
endpoint, writing a commits resource. Enables commit-based contribution
analysis (who commits to another crew's repository).
Together these support downstream review-outcome / unblock-rate scoring and cross-boundary contribution measurement.
Upgrade note: Purely additive and backward-compatible. New MR fields are
nullable / defaulted, so merge-request data stored by earlier versions still
validates on read. list_commits is a new method; no existing method changed.
2026.08.28.1
Changed: Normalized the extension license to Apache-2.0 and corrected the copyright holder to "Sean Escriva". Extensions that previously shipped an MIT LICENSE.md are now Apache-2.0, consistent with the repository root and every other extension. No code or behavioral changes.
Upgrade note: License text only. No API, schema, or runtime behavior changed.
2026.08.26.3
Fixed: Restored inline npm:zod@4.4.3 import specifiers so the registry
quality scorer can resolve dependencies and score the extension. An earlier
release used a bare "zod" import-map specifier, which published but scored as
unscored.
Changed: Retained explicit compilerOptions.strict in deno.json. No
behavioral or schema changes.
Modified 1 behaviors
2026.09.01.1
Added: mergedAt field on merge requests (GraphQL mergedAt, REST
merged_at) — the timestamp an MR was merged, or null if not merged.
Added: approvers field on merge requests — the usernames who approved
(reviewed) the MR, from GraphQL approvedBy. Enables cross-boundary review
attribution (an approver helps the MR author). REST-mapped MRs default to an
empty array.
Added: list_commits method — lists a project's commits (optionally a
branch, with a since lower time bound) via the REST repository/commits
endpoint, writing a commits resource. Enables commit-based contribution
analysis (who commits to another crew's repository).
Together these support downstream review-outcome / unblock-rate scoring and cross-boundary contribution measurement.
Upgrade note: Purely additive and backward-compatible. New MR fields are
nullable / defaulted, so merge-request data stored by earlier versions still
validates on read. list_commits is a new method; no existing method changed.
2026.08.28.1
Changed: Normalized the extension license to Apache-2.0 and corrected the copyright holder to "Sean Escriva". Extensions that previously shipped an MIT LICENSE.md are now Apache-2.0, consistent with the repository root and every other extension. No code or behavioral changes.
Upgrade note: License text only. No API, schema, or runtime behavior changed.
2026.08.26.3
Fixed: Restored inline npm:zod@4.4.3 import specifiers so the registry
quality scorer can resolve dependencies and score the extension. An earlier
release used a bare "zod" import-map specifier, which published but scored as
unscored.
Changed: Retained explicit compilerOptions.strict in deno.json. No
behavioral or schema changes.
Modified 1 behaviors
2026.08.28.1
Changed: Normalized the extension license to Apache-2.0 and corrected the copyright holder to "Sean Escriva". Extensions that previously shipped an MIT LICENSE.md are now Apache-2.0, consistent with the repository root and every other extension. No code or behavioral changes.
Upgrade note: License text only. No API, schema, or runtime behavior changed.
2026.08.26.3
Fixed: Restored inline npm:zod@4.4.3 import specifiers so the registry
quality scorer can resolve dependencies and score the extension. An earlier
release used a bare "zod" import-map specifier, which published but scored as
unscored.
Changed: Retained explicit compilerOptions.strict in deno.json. No
behavioral or schema changes.
2026.08.26.3
Fixed: Restored inline npm:zod@4.4.3 import specifiers so the registry
quality scorer can resolve dependencies and score the extension. An earlier
release used a bare "zod" import-map specifier, which published but scored as
unscored.
Changed: Retained explicit compilerOptions.strict in deno.json. No
behavioral or schema changes.
2026.08.26.1
Fixed: Added missing description field to upgrade entry for version
2026.08.24.2. The omission caused swamp extension pull to fail with a catalog
validation error ("upgrades.N.description: Invalid input: expected string,
received undefined").
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License2/2earned
- Verified public repository2/2earned