9.7 KiB
GitLab Inline Comment Automation
Overview
The post-inline-comment.py helper posts inline code review comments to GitLab merge requests at specific file lines. It is the preferred helper because it can recover from GitLab line_code validation failures by computing the diff line_code and retrying with position[line_range][start/end][line_code].
Why Use Inline Comments?
Problem: GitLab's native glab mr note command only creates general MR comments. When reviewing code, you often want to comment on specific lines, but there's no built-in glab command for this.
Solution: This script uses glab api to call the GitLab Discussions API with proper JSON position data (file path, line number, and commit SHAs). Authentication stays inside glab; the helper never reads token values.
Benefits:
- 📍 Contextualized feedback - Comments appear at exact line locations
- ⏱️ Saves time - No manual clicking in GitLab UI
- 🤖 Enables automation - Can be integrated into review workflows
- ✅ Better UX - Developers see feedback in context
Installation
# The helper is already in the gitlab-cli-skills repo
cd /path/to/gitlab-cli-skills/scripts
chmod +x post-inline-comment.py
# Optional: Add to PATH for global access
ln -s "$(pwd)/post-inline-comment.py" ~/.local/bin/post-inline-comment
Requirements
- glab CLI - Configured and authenticated (
glab auth login) - Python 3 - Stdlib only, no pip install needed
Usage
post-inline-comment.py --project <group/project> --mr <mr_iid> --file <file_path> --line <line_number> --body <comment_text>
Parameters
| Parameter | Description | Example |
|---|---|---|
project |
Repository path | owner/repo |
mr |
Merge request IID (numeric) | 42 |
file |
File path relative to repo root | src/main.js |
line |
Line number in NEW version | 100 |
body |
Comment (supports markdown) | Bug: Add null check |
Examples
Simple comment:
post-inline-comment.py \
--project "owner/repo" \
--mr 42 \
--file "src/components/Button.tsx" \
--line 25 \
--body "Consider using a more descriptive variable name here"
Bug report with markdown:
post-inline-comment.py \
--project "owner/repo" \
--mr 42 \
--file "src/utils/validator.js" \
--line 10 \
--body "**Bug**: This regex doesn't handle edge case when input is \`null\`. Add: \`if (!input) return false;\`"
Multiple comments (batch review):
python3 post-inline-comment.py --project "owner/repo" --mr 42 --batch comments.json
Example comments.json:
[
{ "file": "src/api.js", "line": 15, "body": "Add error handling" },
{ "file": "src/api.js", "line": 22, "body": "Use async/await instead of .then()" },
{ "file": "src/types.ts", "line": 8, "body": "Missing JSDoc comment" }
]
How It Works
- Uses
glab apifor authenticated requests with normalglabauth and environment precedence, without reading token values - Verifies the selected host/account with a read-only
/userrequest and prints the hostname and username before any discussion write - Fetches MR metadata and all diff pages to get:
- Project ID
- Base SHA (target branch commit)
- Head SHA (source branch commit)
- Start SHA (merge base commit)
- Raw file diff text for anchor recovery
- Actual
old_path/new_pathvalues for renamed-file anchors
- Builds JSON payload with position data:
{ "body": "Comment text", "position": { "base_sha": "abc123...", "head_sha": "def456...", "start_sha": "abc123...", "position_type": "text", "new_path": "src/file.js", "new_line": 42 } } - Posts to GitLab API through
glab api --input -with a literal JSON body - If GitLab rejects the simple payload with a
line_codevalidation error:- Parse the file diff
- Derive the correct old/new diff line pair for the target line
- Compute
sha1(diff_path) + '_' + oldLine + '_' + newLine - Retry using
position[line_range][start/end][line_code] - Reuse the diff's actual
old_path/new_pathwhen the file was renamed
- Validates response - Checks that the returned discussion note has a non-null
position(inline) rather than a general discussion
Output
Success Output
GitLab host: gitlab.example.com
GitLab user: reviewer
Fetching current HEAD SHAs for MR !42...
head_sha: 0123456789ab...
Posting: src/main.js:42
Body length: 26 characters
✅ INLINE (line_code retry) | disc_id: abc123
==================================================
Summary: 1 inline ✅ 1 retried-with-line_code 🔁 0 general ⚠️ 0 failed ❌
Discussion IDs: ["abc123"]
The helper emits human-readable status and a final JSON array of discussion IDs; it does not print the full API response or raw comment bodies.
Error Output
❌ FAILED: glab api failed: {"message":{"position":["Position is invalid"]}}
==================================================
Summary: 0 inline ✅ 0 retried-with-line_code 🔁 0 general ⚠️ 1 failed ❌
Common errors:
- 400 Bad Request - Line number doesn't exist in diff
- 401 Unauthorized -
glabis not authenticated for the selected host, or its configured credentials are expired - 404 Not Found - MR or repo doesn't exist
Troubleshooting
glab authentication errors
Cause: glab is not authenticated for the selected host, or the visible account does not have access to the project/MR.
Fix:
glab auth login
glab auth status # Verify authentication
line_code validation error
Cause: For some MR/file/diff combinations, GitLab rejects the simpler new_line/old_line payload unless the request also includes computed position[line_range][start/end][line_code] values.
Fix:
- Use
post-inline-comment.py, which retries automatically with computedline_code - Verify the target line exists in the MR diff
- Verify the file path matches the diff path exactly
Comment appears as general, not inline
Cause: Inline anchoring still failed after the retry path.
- File path might be incorrect (must be relative to repo root)
- Line number might be outside the diff range
- The target line may not map cleanly to the MR diff
Debug:
# Check the diff to see available lines
glab mr diff 42 --repo owner/repo | grep -A5 -B5 "src/file.js"
Integration with Code Review Workflows
Example: Automated Review Script
#!/bin/bash
# review-mr.sh - Automated code review helper
REPO="$1"
MR_IID="$2"
# Fetch MR diff
DIFF=$(glab mr diff "$MR_IID" --repo "$REPO")
# Check for common issues
if echo "$DIFF" | grep -q "console.log"; then
# Find line number of console.log
LINE=$(echo "$DIFF" | grep -n "console.log" | head -1 | cut -d: -f1)
FILE=$(echo "$DIFF" | grep -B20 "console.log" | grep "^+++" | head -1 | cut -d/ -f2-)
python3 post-inline-comment.py --project "$REPO" --mr "$MR_IID" --file "$FILE" --line "$LINE" \
--body "⚠️ Remove console.log before merging"
fi
# Check for TODO comments
if echo "$DIFF" | grep -q "TODO"; then
# ... similar logic
fi
Example: Review from Analysis Tool
#!/bin/bash
# Run ESLint and post inline comments for each issue
REPO="owner/repo"
MR_IID="42"
# Run linter and parse output
eslint src/ --format json | jq -r '.[] | "\(.filePath):\(.messages[].line) \(.messages[].message)"' | \
while IFS=: read -r file line message; do
# Remove absolute path prefix
rel_path="${file#/absolute/path/to/repo/}"
python3 post-inline-comment.py --project "$REPO" --mr "$MR_IID" --file "$rel_path" --line "$line" --body "ESLint: $message"
done
Limitations
- Line must exist in diff - Can only comment on lines that were added or changed in the MR
- File path must be exact - Must match the path relative to repo root exactly
- New file lines only - Line number refers to the NEW version (after changes)
- HTTPS host roots only - Use
--host https://gitlab.example.comfor self-hosted instances. Userinfo, paths, query strings, fragments, and non-default ports are rejected becauseglab api --hostnamedoes not accept a port.
API Reference
This script uses the GitLab Discussions API.
Endpoint:
POST /projects/:id/merge_requests/:merge_request_iid/discussions
Key fields:
body- Comment text (markdown supported)position.base_sha- Target branch commitposition.head_sha- Source branch commitposition.new_path- File pathposition.new_line- Line number
Development
Testing
Test on a personal repo before using on production MRs:
# Create test MR
glab mr create --title "Test MR" --repo owner/test-repo
# Post test comment
./post-inline-comment.py \
--project "owner/test-repo" \
--mr 1 \
--file "README.md" \
--line 1 \
--body "TEST: This is a test inline comment"
# Verify in GitLab UI
# Delete test comment and MR when done
Contributing
Improvements welcome! This script is part of gitlab-cli-skills.
Ideas for enhancement:
- Support for self-hosted GitLab instances (configurable API URL)
- Batch mode (read comments from file or stdin)
- Support for line ranges (multi-line comments)
- Integration with existing
glab-mrreview workflow
Related Skills
- glab-mr - Main MR management skill
- Code review workflows - Documented in
glab-mr/SKILL.md - CI automation - Can be triggered from CI pipelines
License
Same as gitlab-cli-skills: MIT License
Version: 1.0.0 (2026-02-23)
Tested on: GitLab.com with glab CLI v1.48.0