Draft notes API: updating a draft note without position wipes its diff position
Summary
If you call Update a draft note (PUT /projects/:id/merge_requests/:merge_request_iid/draft_notes/:draft_note_id) with only note, it deletes the diff position of a diff draft note. The draft becomes a general comment, so when the review is published it's posted as a general MR comment instead of on the diff line.
The docs list position as optional ("Position when creating a diff note"), so a caller that only wants to change the text expects the position to stay as it is.
Steps to reproduce
- On an MR with a diff, create a diff draft note:
glab mr note create <iid> --draft --file src/extension.ts --line 9 -m "original"GET .../draft_notes/:idreturns:"position": { "base_sha": "830e3185…", "start_sha": "a377257a…", "head_sha": "a758c5c8…", "position_type": "text", "old_path": "src/extension.ts", "new_path": "src/extension.ts", "new_line": 9, "line_range": null } - Update only the body:
glab api -X PUT projects/<project>/merge_requests/<iid>/draft_notes/<id> -f note="updated" - The response (and every later
GET) now has:"position": { "base_sha": null, "start_sha": null, "head_sha": null, "old_path": null, "new_path": null, "position_type": "text", "old_line": null, "new_line": null, "line_range": null }
I reproduced this on gitlab.com (2026-09-30) with a single-line draft and a line_range draft. It also hit real reviews: 4 diff drafts on gitlab-org/editor-extensions/gitlab-lsp!4253 (merged) lost their positions after their bodies were edited from a CLI.
Expected: leaving out position keeps the existing position. Only a position in the request should change it.
Root cause
lib/api/draft_notes.rb:226, added in f17012fd (2023-07):
draft_note.update!(note: params[:note], position: params[:position])position is an optional param. When it's left out, params[:position] is nil, and that overwrites the stored position.
Proposed fix
Only update position when the request includes it:
attrs = { note: params[:note] }
attrs[:position] = params[:position] if params.key?(:position)
draft_note.update!(attrs)Also add a request spec in spec/requests/api/draft_notes_spec.rb: create a diff draft, PUT with only note, and assert the position is unchanged.
Workaround for API clients
Re-send the existing position with the new body. GET the draft, then PUT with note plus its position (without null fields; line_range.start/end as {line_code, type}). I checked that this keeps both single-line and line_range positions.