GitLab CLI (glab)
Required GitLab CLI preflight
Before any workflow step, read ../../references/gitlab-cli-preflight.md and
complete it. Do not run GitLab workflow commands until glab installation,
latest-version check, authentication, hostname, and active identity are
verified.
Use glab for all GitLab operations. Run glab <command> --help for detailed
flag information from the installed version. For experimental commands such as
mr note, stack, mcp, and work-items, always inspect the exact subcommand
help immediately before use because argument order and flags can change.
Remote-change safety
- Before any write, state the resolved hostname, project, active username, target resource, and intended change.
- Require the user's explicit confirmation immediately before merge, delete, unprotect, pipeline cancel/retry, repository/group settings changes, secret/variable mutation, or token creation/revocation/rotation.
- Never retrieve, echo, log, or summarize secret values. For a secret or CI/CD variable, accept a local environment-variable name or file path and pass the value without placing it in chat or a command-line argument.
- Prefer a read-only preview and report the affected item count before bulk changes. Stop if the resolved host, project, target, or identity is ambiguous.
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: Link issues with #123, MRs with !456, cross-project
with group/project#123.
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]'
When using -f for PUT/POST, pass simple key=value pairs. Array bracket
syntax like ids[]=1 is not supported:
glab api projects/:id/merge_requests/:iid -X PUT -f "assignee_id=1"
Content-type guidance
# -f / --raw-field — literal string value
glab api projects/:id/issues/:iid/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/:iid/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/:iid/notes \
--input /tmp/body.json \
-H "Content-Type: application/json"
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).--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.