GitLab CLI (glab)
glab is pre-configured and available in your environment. Use it for all
GitLab operations. Run glab <command> --help for detailed flag information.
Quick reference
# Issues
glab issue view <iid>
glab issue list --label "bug,priority::1"
glab issue create --title "title" --description "$(cat /tmp/desc.md)"
glab issue note <iid> -m "comment text"
# Merge requests
glab mr create --push --title "fix: title" --description "$(cat /tmp/desc.md)"
glab mr view <iid>
glab mr list --assignee <user>
glab mr update <iid> --description "$(cat /tmp/desc.md)"
glab mr note create <iid> -m "comment text"
# CI/CD
glab ci status
glab ci status --output json
glab ci list
glab ci get --merge-request <iid> --with-job-details
glab ci get --pipeline-id <id> --output json
glab ci retry <job-id>
glab api projects/:id/jobs/<job-id>/trace
# Machine-readable output
glab mr list --output json | jq '.[].title'
Templates: Check .gitlab/merge_request_templates/ and
.gitlab/issue_templates/ for project-specific templates.
References: Always use full URLs in note/comment bodies (e.g.
https://gitlab.com/org/project/-/issues/123) instead of short references
(#123, !456). This applies to issues, merge requests, epics, and so on.
Short refs resolve against project context and render as literal text on
group-level items (epics, group work items); full URLs expand everywhere.
Comments and discussions
Use the mr note subcommands (create, resolve, reopen); flags on the
root glab mr note command are deprecated.
Short, inline bodies — pass -m
glab issue note <iid> -m "comment text"
glab mr note create <iid> -m "comment text"
glab incident note <iid> -m "comment text"
# Cross-project
glab mr note create <iid> -m "..." --repo group/project
Long or Markdown bodies — pipe to stdin (preferred for MR notes)
glab mr note create reads the body from stdin when its input is a pipe.
This avoids shell-quoting pitfalls (backticks, $, backslashes) and is the
safest pattern for non-interactive use.
# From a file
glab mr note create <iid> < /tmp/body.md
# Inline literal multi-line body — quoted heredoc, no shell expansion inside
glab mr note create <iid> << 'EOF'
Your **markdown** comment.
Code blocks and `inline code`, $variables, and \backslashes are all literal.
EOF
glab issue note and glab incident note do not read stdin. For long
bodies on those commands, use glab api with -F body=@file (see
Content-type guidance) or inline a quoted heredoc
into -m:
glab issue note <iid> -m "$(cat << 'EOF'
Your **markdown** comment.
Code blocks and `inline code` are safe.
EOF
)"
For descriptions on glab issue create / glab mr create / glab mr update,
inline a quoted heredoc into --description, or for very large or reusable
bodies write to a file and use --description "$(cat /tmp/desc.md)".
Threaded replies on merge requests
glab mr note create supports --reply <discussion-id> for replying inside
an MR thread. The value can be the full discussion ID or a unique prefix of
at least 8 characters.
Diff comments accept a single line (--line 42), a range (--line 10:15),
a removed line (--old-line 7), or no line for a file-level comment.
glab mr note create <iid> --reply <discussion-id> -m "I agree!"
glab mr note create <iid> --file main.go --line 42 -m "Needs refactoring"
glab mr note create <iid> --file main.go --line 10:15 -m "Extract this block"
glab mr note create <iid> --file main.go --old-line 7 -m "Why was this removed?"
glab mr note create <iid> --file main.go -m "General comment on this file"
glab mr note create <iid> -m "LGTM" --unique # idempotent: skip if same body exists
glab mr note resolve / reopen take the MR identifier followed by the
discussion identifier. The identifier can be a discussion ID (full 40-char
hex or 8+ char prefix) or a note ID (integer; the parent discussion is
looked up automatically):
glab mr note resolve <iid> <discussion-id>
glab mr note resolve <iid> <note-id> # integer note ID also works
glab mr note reopen <iid> <discussion-id>
Threaded replies on issues, incidents, and work items
The CLI does not wrap threaded replies for these, so you fall back to
glab api. For any non-trivial body, write it to a file and post the file
rather than inlining rich Markdown — inlined backticks, $, newlines, and a
leading @ all break (see Content-type guidance):
# Discover the discussion ID
glab api projects/:id/issues/<iid>/discussions \
| jq '.[] | {id, body: .notes[0].body}'
# Build the body in a file, then post it with -F body=@file
cat > /tmp/reply.md << 'EOF'
@user — here's the result, with `code`, a $variable, and an emoji ✅.
EOF
glab api projects/:id/issues/<iid>/discussions/<discussion-id>/notes \
-F body=@/tmp/reply.md
For a short, plain reply you can still inline it with -f body="reply text".
API calls
glab api auto-prepends /api/v4/. Use relative paths:
glab api user # NOT /api/v4/user
glab api projects/:id/merge_requests
glab api projects/:id/issues | jq '.[0]'
Placeholders
glab api expands exactly these tokens in a path, a field value, or inline JSON:
:branch :fullpath :group :id :namespace :repo :user :username
:user and :username work anywhere. The rest are read from the current
repository, so they need a git checkout with a GitLab remote. Outside one, or to
target a different project, supply the value instead:
glab api projects/:id/issues # inside a checkout
glab api projects/:id/issues -R group/project # any project; placeholder still expands
glab api projects/group%2Fproject/issues # literal path -- %2F is required
glab api projects/1234/issues # numeric project ID
Anything else beginning with a colon is not a placeholder. glab sends it to the API verbatim, so the request fails at the server rather than in glab.
Content-type guidance
When using -f for PUT/POST, pass simple key=value pairs. Array bracket
syntax like ids[]=1 is not supported:
# -f / --raw-field — literal string value
glab api projects/:id/merge_requests/42 -X PUT -f "assignee_id=1"
glab api projects/:id/issues/42/notes -f body="comment text"
# -F / --field — reads @file as a string. The leading @ means "read this
# file", so only pass a real path here. A literal body that starts with @
# (e.g. "@user thanks") must NOT go through -F — it would be read as a
# filename. Use -f for literal inline text, or write the body to a file and
# point -F at the file (recommended for rich/markdown bodies).
glab api projects/:id/issues/42/notes -F body=@/tmp/comment.md
# --input — raw request body from a file (or '-' for stdin). Does NOT set
# Content-Type. Without the header, JSON endpoints return HTTP 415.
glab api projects/:id/issues/42/notes \
--input /tmp/body.json \
-H "Content-Type: application/json"
# --form — multipart/form-data. Required for endpoints that take a real file
# upload, such as project or group uploads and wiki attachments. -F does not
# do this: it reads the file into a text field, and the endpoint rejects the
# request with HTTP 400.
#
# IMPORTANT: --form is mutually exclusive with -f, -F, and --input. Every
# field in a multipart request must use --form
glab api projects/:id/uploads --method POST --form "file=@screenshot.png"
# Wiki attachment — both fields must use --form
glab api projects/:fullpath/wikis/attachments --method POST \
--form "file=@screenshot.png" --form "branch=main"
Arrays and nested objects
-F / --field parses a value that starts with [ or { as JSON, so arrays
and nested objects go inline without a file. Placeholders are expanded inside
the JSON. Invalid JSON returns an error rather than being sent as a string.
# Array of strings
glab api -X PUT projects/:id -F 'topics=["my-topic","GitLab"]'
# Nested object, with a placeholder expanded inside it
glab api projects/:id/merge_requests/42/discussions -X POST \
-F body="looks good" \
-F 'position={"position_type":"text","new_path":"main.go","new_line":42}'
# Empty array clears a field
glab api -X PUT projects/:id -F 'topics=[]'
-f / --raw-field never parses JSON: a bracketed value like
-f 'scopes=[api,read_api]' is sent as the literal string. Use -F with real
JSON for arrays. On GET and DELETE requests, and whenever --input is used,
-F arrays are serialized as repeated key[]= query parameters.
Common mistakes
-mis required onnotecommands — without it,glab issue noteandglab incident noteopen$EDITOR(which hangs in non-interactive environments).glab mr note createfalls back to reading stdin on a pipe, but still opens$EDITORon a TTY.- Use
glab mr note create, notglab mr note -m— the--message,--unique,--resolve, and--unresolveflags on the rootglab mr notecommand are deprecated. Use thecreate,resolve, andreopensubcommands instead. - Editor-opening flags are unsafe in agent environments — avoid
--description "-"onissue create/mr create/mr updateand avoid omitting-monnotecommands. Pass an explicit value or pipe from stdin instead. glab issue noteandglab incident noteonly post root-level comments — useglab mr note create --replyfor MRs, orglab api .../discussions/<id>/notesfor issues/incidents (write the body to a file and pass-F body=@filefor anything non-trivial).-Fdoes not upload files —-F file=@x.pngsends the file contents as a string field, and upload endpoints reject it with HTTP 400. Use--form file=@x.pngfor multipart uploads.-Freads a file only to fill a text field, such as a note body. Also,--formis mutually exclusive with-f,-F, and--input— every field in a multipart request must use--form(e.g.--form "file=@x.png" --form "branch=main").--inputrequires an explicitContent-Typeheader —glab api --input file.jsonsends raw bytes without setting Content-Type, causing HTTP 415. Add-H "Content-Type: application/json"or use-f/-Finstead.glab ci retrytakes a job ID, not a pipeline ID — to retry an entire pipeline, useglab api projects/:id/pipelines/<id>/retry -X POST.glab ci tracestreams — it blocks until the job finishes. For agents, useglab ci getfor pipeline state orglab api projects/:id/jobs/<job-id>/traceto fetch a finished log.glab ci viewis interactive — terminal UI that blocks. Useglab ci statusorglab ci getfor pipeline state instead.- Always
--pushonglab mr create— without it the remote branch may not exist and MR creation fails. - No
--stateonmr list— use--all,--merged, or--closed. - No
--bodyflag —--bodyis aghflag.glabuses--description. - Labels —
--labelto add,--unlabelto remove. Scoped labels likestatus::doingauto-replace within their scope.