Skip to content
GitmoapdocsBeta
Guide· How do I…?

Merge request discussion

Overview

Discussion is where a merge request stops being a diff and becomes a decision. You leave a general comment for the change as a whole, or an inline comment pinned to one line of one file, and each of those starts a thread anyone with Developer access can reply in. When the feedback has been dealt with you resolve the thread, and a repository — or the group above it — can be set up so that a merge waits until every thread is resolved.

Reactions are the cheap half of the same conversation: eight emoji, each of which you can place once on a given comment, for the agreement or the acknowledgement that does not deserve a paragraph. Commenting or reacting also makes you a participant on the merge request, which is how the platform knows who was actually involved in a change.

Before you start

  • A merge request you can read.Discussion inherits the repository's visibility. A public merge request answers anyone, including callers with no credential; an internal one answers anyone signed in; a private one answers only its members, from Reporter up, and returns 404 to everybody else, so its existence stays private.
  • Developer access to write.Commenting, replying, resolving and reopening threads all need Developer on the repository. Editing or deleting your own comment needs nothing beyond that; editing or deleting somebody else's needs Maintainer.
  • Reporter access — and real membership — to react.Reacting is a write, so there is no shortcut for anonymous or signed-in-but-not-a-member readers of a public repository. You need to be a member at Reporter or above.
  • A token with the right scope, if you are calling the API or the CLI rather than clicking: mr_comment:read to read, mr_comment:create to comment and reply, mr_comment:update to edit a comment and to resolve or reopen a thread, mr_comment:delete to delete, and mr_reaction:manage to add or remove a reaction. Scopes are chosen when you mint a token and cannot be added afterwards, so a review bot that only comments should be given only mr_comment:create.
  • The identifiers in the path.The repository is addressed by its id; the merge request is addressed by its number — the one in its URL and on its card, counted per repository, so !42 in one repository is unrelated to !42 in another. A comment is addressed by the id a read gives you back, and it is only resolved inside the merge request named in the same path.
  • An open merge request, to add to the discussion.A new comment, a reply, and resolving or reopening a thread are all refused once the merge request is merged or closed. Reading it, reacting to a comment, editing a comment that is already there and deleting one all keep working whatever state the merge request is in.
  • A file path and a line number, for an inline comment.Take both from the diff you are reading — the same file path git uses, and the line number on the side of the diff you mean.

How it works

A discussion is a flat list of threads. A thread is one root comment plus its replies, and that is the whole hierarchy: replies cannot have replies.

merge request !42
├─ thread ── general root comment resolved: false thumbs_up 3 rocket 1
│ ├─ reply thumbs_up 1
│ └─ reply
└─ thread ── inline root comment resolved: true heart 2
│ lib/parser.rs, new line 118
└─ reply

Two kinds of root comment. A general comment is about the change; an inline comment is about a line. Which one you get is decided by what you send, not by a type field you set: a file path and at least one line number makes it inline, and nothing makes it general.

Replies are neither. A reply is a reply to a root, and it inherits its context from the thread rather than carrying its own. That is why sending a file path together with a parent comment is refused rather than quietly ignored — the reply would be claiming a position the thread already has.

Resolution is a property of the root, not of a person. Any Developer can resolve any thread, including one they did not start; the thread records who resolved it and when. Replies cannot be resolved, and resolving an already-resolved thread is refused rather than treated as a no-op, so an automation that resolves in a loop finds out instead of silently drifting.

Unresolved threads can hold a merge. When the unresolved-threads requirement is turned on for a repository or the group above it, the merge check counts every unresolved root thread on the merge request — general as well as inline. A clean-looking diff with one open general thread still blocks, which is the single most common surprise on this page.

An inline comment is pinned, never moved. It records the file path, the old and new line numbers, and the merge request's two branch heads at that moment — the target branch head and the source branch head. Later pushes do not reposition it and do not delete it: the position you reviewed is preserved, and that commit pair is what tells you the comment predates the current head. Nothing silently reattaches your comment to an unrelated line.

Reactions are per person, per emoji, per comment. The eight values are thumbs_up, thumbs_down, laugh, hooray, confused, heart, rocket and eyes. Adding one you already placed succeeds and changes nothing, so a double-tap is harmless. Reactions work on replies as well as roots, and on a merged or closed merge request as well as an open one. Every comment you read comes back with its own reaction summary already attached — a count per emoji, who placed each one, and a flag saying whether one of them is yours — so a timeline read needs no follow-up call per comment.

Everything reads oldest first. The timeline, a thread's replies and the grouped discussion view are all in creation order, so a page boundary never reshuffles what you already read.

How to

Comment on a merge request

A general comment needs nothing but text. It becomes the root of a new thread anyone with Developer access can reply in, and it starts life unresolved.

Open the merge request and scroll to the box at the foot of the Overview tab. Type your comment — it is markdown, so lists, code fences and links all render — and choose Comment. It appears at the bottom of the timeline with a Reply action underneath it.

Terminalbash
gitmoap mr-comments create --repo-id <id> --mr-id <id> --body <body>
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "body": "<string>" }'

The text is between 1 and 10,000 characters after trimming, so a comment of nothing but spaces is refused the same way an empty one is. If you have more to say than that, say it in the merge request description and link to it.

Comment on a line of the diff

An inline comment carries a file path and at least one line number. Give new_line for a line as it appears after the change, old_line for a line as it appeared before, and both when you mean a line that was modified.

Open the merge request's Changes tab and hover the line you want to talk about. The + that appears in the gutter opens a comment box anchored to that line; the file and line are filled in for you. Choose Comment to post it, and it renders in place in the diff and in the timeline.

Terminalbash
gitmoap mr-comments create --repo-id <id> --mr-id <id> --body <body>
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "body": "<string>" }'

The comment is stored against the exact position you chose and against the merge request's target and source branch heads at that moment. When the author pushes again, your comment stays where it was rather than following the line, so a reviewer reading later can see what was actually reviewed.

Reply in a thread

A reply names the root comment it belongs to. Reply to the root even when the thing you are answering is halfway down the thread — there is only one level, and a reply that names another reply is refused.

Choose Reply under any comment in the thread — root or reply, it lands in the same place — type your text and choose Reply. The thread expands in place and the root's reply count goes up by one.

Terminalbash
gitmoap mr-comments create --repo-id <id> --mr-id <id> --body <body>
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "body": "<string>" }'

If you have a reply's id and need its root, the reply carries the thread's id alongside its own; that id is the root comment.

Read the whole discussion

The grouped view returns every thread on the merge request, each as a root plus its replies in order, in one call. It is the right read for rendering a review page or auditing a merge request end to end, because it needs no second call per thread.

The merge request's Overview tab is this view: every thread in creation order with its replies underneath, and resolved threads collapsed to a single line you can expand.

Terminalbash
gitmoap mr-comments list-mr-discussions --repo-id <id> --mr-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/discussions \
-H "Authorization: Bearer $GITMOAP_TOKEN"

It takes no filters and no page size — the answer is the whole discussion, however long it has grown, in one response. On a merge request carrying hundreds of comments, page the timeline instead and expand only the threads you need.

Page through the timeline

The timeline returns root comments only, paged, and it takes filters the grouped view does not: by type, so you can pull just the inline comments for a file-by-file pass, and by resolution, so you can pull exactly what is still open.

Above the discussion, use All / Unresolved to filter by resolution and All / Inline to filter by type. The counter beside Unresolved is the same number the merge check uses.

Terminalbash
gitmoap mr-comments list --repo-id <id> --mr-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Filtering by unresolved is the fastest way to answer "what is still blocking this merge" — it returns general and inline threads together, which is what the merge check counts.

Expand a thread's replies

A root comment carries a reply count, not the replies themselves, so a long thread stays cheap to list. Fetch the replies for one thread when you need them, by the root's id.

Choose Show N replies under a root comment. Replies load oldest first, and load more as you scroll if the thread is long.

Terminalbash
gitmoap mr-comments list-replies \
--repo-id <id> \
--mr-id <id> \
--comment-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/replies \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Give the root comment's id. Asking for the replies of a reply is refused rather than returning an empty list, because an empty list would look like a thread with no answers.

Look up a single comment

Useful when you have a comment id — from a permalink somebody sent you, or from a listing you have already paged — and want that one comment back with its author, its position and its reactions resolved.

Every comment has a timestamp that is a permalink. Open it, or copy it from the … menu on the comment, and it takes you to the comment in its thread.

Terminalbash
gitmoap mr-comments get --repo-id <id> --mr-id <id> --comment-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Edit a comment

Only the text can change. Everything else about a comment — its author, its thread, an inline comment's position — is fixed at creation.

Choose … on your comment, then Edit. Change the text and choose Save. The comment shows an edited marker afterwards.

Terminalbash
gitmoap mr-comments edit \
--repo-id <id> \
--mr-id <id> \
--comment-id <id> \
--body <body>
curlbash
curl -X PATCH https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "body": "<string>" }'

A comment is marked as edited on the first change and stays marked; there is no way to clear it, and the timestamp shows when the last edit happened. You can edit your own comment with Developer access; editing anyone else's needs Maintainer.

Delete a comment

Warning

Deleting a root comment deletes the whole thread: every reply anybody wrote under it, and every reaction on the root and on each of those replies. It is immediate, it cannot be undone, and the people whose replies you removed are not asked. Delete a reply instead if you only meant to remove your own words, or edit the root if you meant to correct it.

Deleting a reply removes only that reply and drops the root's reply count by one. Deleting the merge request removes its entire discussion with it.

Choose … on the comment, then Delete. The confirmation says how many replies go with it; Delete is immediate.

Terminalbash
gitmoap mr-comments delete --repo-id <id> --mr-id <id> --comment-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

You can delete your own comment with Developer access. Deleting somebody else's needs Maintainer — one level above commenting, because it destroys another person's contribution to a review.

Resolve a thread

Resolving says the feedback has been dealt with. It does not require you to be the author of the thread or of the merge request: any Developer can resolve any thread, which is what lets a reviewer close out their own point without waiting for a round trip.

Choose Resolve thread at the top of the thread. It collapses to one line naming who resolved it and when, and the unresolved counter above the discussion goes down.

Terminalbash
gitmoap mr-comments resolve-mr-thread \
--repo-id <id> \
--mr-id <id> \
--comment-id <id>
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/resolve \
-H "Authorization: Bearer $GITMOAP_TOKEN"

The merge request has to be open. Resolving a thread on a merged or closed merge request is refused, as is resolving a thread that is already resolved, or naming a reply instead of the thread's root.

Reopen a resolved thread

Reopening puts the feedback back on the merge request and back into the count that can hold a merge. It needs Developer access and an open merge request, exactly as resolving does, and it is refused on a thread that is not resolved.

Choose Unresolve on a collapsed thread. It expands again and returns to the unresolved count.

Terminalbash
gitmoap mr-comments unresolve-mr-thread \
--repo-id <id> \
--mr-id <id> \
--comment-id <id>
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/unresolve \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Reopening clears who resolved it and when, so the thread reads as open rather than as open-again. Both the resolve and the reopen appear in the merge request's activity, so the history is not lost.

React to a comment

Hover a comment and choose the emoji button, then pick from the eight. Your own reactions are highlighted, and the count beside each emoji is everyone's.

Terminalbash
gitmoap mr-comments add-comment-reaction \
--repo-id <id> \
--mr-id <id> \
--comment-id <id> \
--reaction thumbs_up|thumbs_down|laugh|hooray|confused|heart|rocket|eyes
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/reactions \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "reaction": "<one of confused | eyes | heart | hooray | laugh | rocket | thumbs_down | thumbs_up>" }'

Adding a reaction you have already placed succeeds and changes nothing — the call is safe to retry. The response is the comment's whole reaction summary, so a client can redraw the row from one answer rather than re-reading the comment.

Remove your reaction

Note

Removing a reaction removes yours, and only yours. There is no way to clear somebody else's reaction, and no way to clear all of them at once short of deleting the comment — which takes the comment and its replies with it.

Choose your highlighted emoji again to take it back. The count drops by one and the highlight goes.

Terminalbash
gitmoap mr-comments remove-comment-reaction \
--repo-id <id> \
--mr-id <id> \
--comment-id <id> \
--reaction <reaction>
curlbash
curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/reactions/{reaction} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Removing a reaction you never placed is a 404, not a silent success, so a script can tell the difference between "removed" and "there was nothing there".

See who reacted

Hover the emoji count on a comment. The tooltip names everyone who placed it.

Terminalbash
gitmoap mr-comments list-comment-reactions \
--repo-id <id> \
--mr-id <id> \
--comment-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/reactions \
-H "Authorization: Bearer $GITMOAP_TOKEN"

The summary groups by emoji, omits emoji nobody used, and tells you which ones are yours. Reading a comment already includes this, so reach for the dedicated read when you want to refresh one comment's reactions after a toggle without re-reading the comment.

Reference

12 endpoints

GET/repositories/{repo_id}/merge-requests/{mr_id}/discussionsmr_comment:read

List all comments grouped by thread (discussions)

list_mr_discussionsGET /repositories/{repo_id}/merge-requests/{mr_id}/discussionsOpen in the API reference

Token scope

mr_comment:read

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found — or found and private, which is masked as absent

Receive

  • []object1 field
    • rootobject
GET/repositories/{repo_id}/merge-requests/{mr_id}/commentsmr_comment:read

List root comments for a merge request (timeline)

list_mr_commentsGET /repositories/{repo_id}/merge-requests/{mr_id}/commentsOpen in the API reference

Token scope

mr_comment:read

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
Query
  • comment_typestringoptionalFilter by type: general or inline
  • resolvedstringoptionalFilter by resolved: true or false
  • limitint32optionalPage size (default 20, max 100)
  • next_tokenstringoptionalPagination token

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found — or found and private, which is masked as absent

Receive

  • next_tokenobjectoptional
GET/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}mr_comment:read

Get a single comment

get_mr_commentGET /repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}Open in the API reference

Token scope

mr_comment:read

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
  • comment_iduuidComment ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Comment not found

Receive

No body.

POST/repositories/{repo_id}/merge-requests/{mr_id}/commentsmr_comment:create

Create a comment on a merge request

create_mr_commentPOST /repositories/{repo_id}/merge-requests/{mr_id}/commentsOpen in the API reference

Token scope

mr_comment:create

Counts against

comments_per_mr

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
Body
  • bodystring
  • file_pathobjectoptional
  • new_lineint32optional
  • old_lineint32optional
  • parent_idobjectoptional

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository or merge request not found
  • 409The request conflicts with the resource's current state; `reason` says how
  • 429Comment limit reached for this merge request

Receive

No body.

PATCH/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}mr_comment:update

Edit a comment body

edit_mr_commentPATCH /repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}Open in the API reference

Token scope

mr_comment:update

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
  • comment_iduuidComment ID
Body
  • bodystring

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Comment not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

DELETE/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}mr_comment:delete

Delete a comment

delete_mr_commentDELETE /repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}Open in the API reference

Token scope

mr_comment:delete

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
  • comment_iduuidComment ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Comment not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

GET/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/repliesmr_comment:read

List replies for a comment thread

list_mr_comment_repliesGET /repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/repliesOpen in the API reference

Token scope

mr_comment:read

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
  • comment_iduuidRoot comment ID
Query
  • limitint32optionalPage size (default 50, max 100)
  • next_tokenstringoptionalPagination token

Refuses

  • 400The comment is itself a reply; give the root comment's id
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Comment not found

Receive

The same shape list_mr_comments returns — 1 field. Go to it

Show it here
  • next_tokenobjectoptional
POST/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/resolvemr_comment:update

Resolve a comment thread (general or inline)

resolve_mr_threadPOST /repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/resolveOpen in the API reference

Token scope

mr_comment:update

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
  • comment_iduuidRoot comment ID (general or inline)

Refuses

  • 400Comment is a reply or already resolved
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Comment not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

POST/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/unresolvemr_comment:update

Unresolve a comment thread (general or inline)

unresolve_mr_threadPOST /repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/unresolveOpen in the API reference

Token scope

mr_comment:update

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
  • comment_iduuidRoot comment ID (general or inline)

Refuses

  • 400Comment is a reply or not resolved
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Comment not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

GET/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/reactionsmr_comment:read

List the reactions on a merge-request comment

list_comment_reactionsGET /repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/reactionsOpen in the API reference

Token scope

mr_comment:read

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
  • comment_iduuidComment ID

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository, merge request, or comment not found

Receive

  • comment_iduuid
  • reactions[]object3 fields
    • countint32
    • mebooleanWhether you placed this reaction.
    • reactionconfusedeyeshearthooraylaughrocketthumbs_downthumbs_upThe emoji reactions that can be placed on a comment — the same set GitHub offers.
POST/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/reactionsmr_reaction:manage

Add an emoji reaction to a merge-request comment

add_comment_reactionPOST /repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/reactionsOpen in the API reference

Token scope

mr_reaction:manage

Counts against

reactions_per_comment

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
  • comment_iduuidComment ID
Body
  • reactionconfusedeyeshearthooraylaughrocketthumbs_downthumbs_upThe emoji reactions that can be placed on a comment — the same set GitHub offers.

Refuses

  • 400Invalid reaction
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — membership required to react
  • 404Repository, merge request, or comment not found
  • 409The request conflicts with the resource's current state; `reason` says how
  • 429Reaction limit reached for this comment

Receive

The same shape list_comment_reactions returns — 2 fields. Go to it

Show it here
  • comment_iduuid
  • reactions[]object3 fields
    • countint32
    • mebooleanWhether you placed this reaction.
    • reactionconfusedeyeshearthooraylaughrocketthumbs_downthumbs_upThe emoji reactions that can be placed on a comment — the same set GitHub offers.
DELETE/repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/reactions/{reaction}mr_reaction:manage

Remove the caller's emoji reaction from a merge-request comment

remove_comment_reactionDELETE /repositories/{repo_id}/merge-requests/{mr_id}/comments/{comment_id}/reactions/{reaction}Open in the API reference

Token scope

mr_reaction:manage

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
  • comment_iduuidComment ID
  • reactionconfusedeyeshearthooraylaughrocketthumbs_downthumbs_upReaction type: thumbs_up, thumbs_down, laugh, hooray, confused, heart, rocket, eyes

Refuses

  • 400Invalid reaction
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — membership required to react
  • 404Reaction not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

Limits

Two ceilings apply to discussion, and both are per plan.

What is countedWhere it is countedFreeProTeamEnterprise
Comments, including repliesone merge request5002,0005,00010,000
Reactions, everybody's togetherone comment1002505001,000

Comments are counted across the whole merge request, and a reply counts the same as a root comment — a merge request with 400 roots and 100 replies is at the Free ceiling. Reactions are counted per comment and across everyone: one per person per emoji, so a comment carrying all eight emoji from twelve people each is at 96.

The table above is the tier default. The number that actually applies to a namespace — the default with any approved increase folded in — is published on that namespace's limits view alongside every other plan limit, which is where a client should read it rather than hard-coding the column.

Two ways to get more room. Moving the namespace to a higher plan raises both ceilings to the column above. If you need one raised without changing plan, an Owner can request an increase for that namespace; an approved increase is capped at 50,000 comments per merge request and 10,000 reactions per comment, which are the highest values any override can reach.

If a real review is approaching the comment ceiling rather than a script, the merge request is doing too much. Splitting it is cheaper than raising the limit, and it reviews better.

Troubleshooting

400 BAD_REQUESTnaming repo_id, mr_id or comment_id
Cause
A path value is not the type it should be — usually a repository path like my-group/my-repo pasted where the repository id belongs, or something that is not a whole number where the merge request's number belongs
What to do
Use the repository's id, the merge request's number as it appears in its URL, and a comment id exactly as a read returned it
400 BAD_REQUEST, must be 1-10000 characters
Cause
The comment text is empty, is nothing but whitespace, or is longer than 10,000 characters after trimming
What to do
Send at least one non-blank character. If it is the length, put the substance in the merge request description or a work item and link to it from a shorter comment
400 BAD_REQUEST, Cannot comment on a closed or merged merge request
Cause
Writing to a merge request that is no longer open
What to do
Reopen it if it was closed and the discussion should continue; otherwise comment on the commit or open a work item for the follow-up
400 BAD_REQUEST, Cannot reply to a reply
Cause
The parent you named is itself a reply, and threading is one level deep
What to do
Reply to the thread's root. A reply carries the thread's id alongside its own, and that id is the root
400 BAD_REQUEST, Inline fields … are not allowed on replies
Cause
A file path or a line number was sent together with a parent comment
What to do
Drop the position — the reply takes its context from the thread. To pin a different line, start a new inline thread
400 BAD_REQUEST, At least one of old_line or new_line must be set
Cause
An inline comment named a file but no line
What to do
Send new_line for a line after the change, old_line for a line before it, or both for a modified line
400 BAD_REQUEST, old_line and new_line require file_path
Cause
A line number was sent with no file to anchor it to
What to do
Add the file path from the diff, or drop the line numbers to post it as a general comment
400 BAD_REQUEST, file_path cannot be empty for inline comments
Cause
The file path was sent as an empty string rather than left out
What to do
Leave file_path out entirely to post a general comment; an empty string is not the same as absent
400 BAD_REQUESTnaming a field you did not expect
Cause
The request body carried a field the endpoint does not define — a typo such as line for new_line, or a field copied from another API
What to do
On a new comment send only body, parent_id, file_path, old_line and new_line; on an edit send only body. Unrecognised fields are rejected rather than ignored, so a typo never silently posts the wrong kind of comment
400 BAD_REQUESTon listing replies, provide the root comment ID
Cause
The id you asked for is a reply, and replies have no replies
What to do
Use the thread's root id, which every reply carries
400 BAD_REQUEST, Thread is already resolved or Thread is not resolved
Cause
The thread is already in the state you asked for
What to do
Read the thread's resolution before flipping it, or treat this refusal as success in an automation that is reconciling
400 BAD_REQUEST, Can only resolve root comments, not replies
Cause
The comment id names a reply
What to do
Resolve the thread's root; resolution belongs to the thread, not to one message in it
400 BAD_REQUEST, Can only resolve threads on open merge requests (or the same about unresolving)
Cause
The merge request is merged or closed
What to do
Reopen it if it was closed and the thread still needs closing out. A merged merge request's threads keep the state they had at merge, and that state is final
400 BAD_REQUESTon a reaction
Cause
The emoji is not one of the eight the platform accepts
What to do
Use thumbs_up, thumbs_down, laugh, hooray, confused, heart, rocket or eyes, spelled exactly like that
400 BAD_REQUEST, Invalid resolved
Cause
The resolution filter was given something other than true or false
What to do
Send true or false, or leave the filter off to get both
401 UNAUTHORIZED
Cause
No credential was sent, or the one sent has expired or been revoked. Reading a public merge request needs no credential, but every write does
What to do
Sign in again, or mint a fresh token. A revoked token is indistinguishable from an invalid one, so check it has not been revoked before debugging further
403 FORBIDDENon commenting or resolving
Cause
You can read the repository but you are below Developer
What to do
Ask an Owner or Maintainer to raise your access level. Reading the discussion works at Reporter, and at Guest on a public or internal repository
403 FORBIDDENon editing or deleting a comment you did not write
Cause
Acting on somebody else's comment needs Maintainer, one level above writing your own
What to do
Ask a Maintainer to remove it, or ask its author to edit it. Your own comments stay editable at Developer
403 FORBIDDENon a reaction, on a repository you can read
Cause
Reacting is a write, so it needs real membership — signed in but not a member of a public repository is not enough
What to do
Ask to be added to the repository or one of its groups at Reporter or above
403 INSUFFICIENT_SCOPE
Cause
The token is valid but was minted without the scope this call needs
What to do
Mint a new token carrying mr_comment:create, mr_comment:update, mr_comment:delete or mr_reaction:manage as appropriate. Note that resolving a thread needs the update scope, not the create scope
404 NOT_FOUNDon a merge request you are sure exists
Cause
Either the repository is private and you are not a member, or the number belongs to a different repository
What to do
A private repository answers 404 rather than 403 to non-members so its existence stays private. Check your membership, then check that the number came from this repository — numbers are per repository and repeat across them
404 NOT_FOUNDon a comment id you copied
Cause
The comment belongs to a different merge request, or it has been deleted — including as a reply under a root somebody else removed
What to do
Re-read the discussion. A comment id is only resolved inside the merge request in the path, so this is also the answer when a parent_id was taken from another merge request's thread. A deleted root takes its replies with it, so a reply id can stop resolving without anyone touching that reply
404 NOT_FOUNDwhen removing a reaction
Cause
You never placed that emoji on that comment, or you already removed it
What to do
Read the comment's reactions first; the summary flags which ones are yours
The timeline comes back empty when you filter by type
Cause
The type filter takes general or inline, and an unrecognised value matches nothing rather than being refused. reply also matches nothing, because the timeline carries root comments only
What to do
Filter by general or inline, or drop the filter to get both. Replies come from the per-thread read, not the timeline
A reaction is attributed to deleted-user
Cause
The account that placed it no longer exists; the reaction itself stays on the comment, because only its own placer can take it back
What to do
Leave it — the tally is still correct. If the row has to go, delete the comment it sits on, which takes its reactions with it (and, on a root, the whole thread)
The merge button reports unresolved threads but the diff looks clean
Cause
The count includes general threads, not just inline ones
What to do
Filter the timeline by unresolved with no type filter — the open thread is usually a general one at the foot of the discussion
An inline comment sits against a line that has since moved
Cause
Inline comments are pinned to the position and the commits they were written against, and are never repositioned by a later push
What to do
Read it against the commit pair it carries, and start a new thread on the current line if the point still stands
A reply you posted is not in the thread you expected
Cause
The parent id named a different thread's root — easy to do when two root comments were copied from the same page
What to do
Delete the misplaced reply and post it again against the right root
A comment shows as edited and you cannot clear the marker
Cause
The marker is set on the first edit and never cleared
What to do
Delete the comment and post the text again if the marker genuinely must not be there — but a root delete takes the thread with it, so prefer a reply saying what changed. The previous text is not kept anywhere, so the marker is the only trace an edit leaves
A whole thread vanished after somebody deleted one comment
Cause
The deleted comment was the thread's root, which takes every reply and every reaction with it
What to do
Repost what still matters. There is no undo, and no copy is kept

FAQ

Can I comment on a merge request after it has merged?

No. A new comment, a reply, and resolving or reopening a thread all need the merge request to be open. Reacting to a comment, editing one and deleting one still work, and reading stays available for good — a merged merge request's discussion is the record of why the change landed.

Who can resolve a thread I started?

Anyone with Developer access on the repository. Resolution is a statement about the feedback, not about ownership, so a reviewer can close out their own point and an author can resolve a thread they answered. The thread records who did it.

Do replies count against the comment limit?

Yes. The ceiling counts every comment on the merge request, roots and replies alike.

Can I react on behalf of a service account?

Yes, with a token carrying mr_reaction:manage and Reporter access for that account. The reaction is recorded against the service account, and it is listed under the account's name. The me flag is relative to whoever is reading: true when that token reads the comment back, false when you read it as yourself.

Is there a way to see only the threads still open?

Filter the timeline by unresolved. That is the same set the merge check counts, which makes it the honest answer to "what is left".

  • Merge requestscreating, updating, merging and closing the merge request the discussion hangs off
  • Reviews and approvalsreviewers, approval and rejection, which are a separate axis from threads
  • Merge request settingsthe repository and group settings that decide whether unresolved threads block a merge
  • Diffs and comparisonsreading the diff an inline comment anchors to, and getting the file and line to anchor with
  • Work item discussionthe same threading, replies, resolution and reactions on work items instead of merge requests
  • Access tokensminting a token and choosing the scopes a commenting bot needs
  • Billing and planswhat each plan raises, and how to ask for a single limit to be increased