Skip to content

fix(gitlab): diff-aware anchors and note fallback for review posting - #124

Merged
factory-nizar merged 2 commits into
devfrom
nizar/gitlab-anchor-fallback
Aug 13, 2026
Merged

fix(gitlab): diff-aware anchors and note fallback for review posting#124
factory-nizar merged 2 commits into
devfrom
nizar/gitlab-anchor-fallback

Conversation

@factory-nizar

@factory-nizar factory-nizar commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Problem

On a customer MR, gitlab-post-review failed to post both approved findings with an opaque GitLab API 400: Bad Request, then hard-failed the job.

Root cause: buildPosition() sends new_line-only positions for every RIGHT-side comment, but GitLab's discussions API requires:

Line type Required position fields
Added line new_line only
Removed line old_line only
Unchanged context line both old_line and new_line
Line not in the diff cannot be anchored at all

Both findings targeted lines the MR never touched, so every payload shape was refused, and the posted === 0 guard turned two bad anchors into a red pipeline. On top of that, the actual GitLab error body (line_code must be a valid line code-style detail) was captured in GitlabApiError.body but never shown, making this undebuggable from CI logs.

Fix

  • Diff-aware anchoring: fetch the MR changes once, build a per-file line index (buildDiffIndex), and shape each position to the line's actual role in the diff — added → new_line, removed → old_line, context → both numbers.
  • Plain-note fallback: a comment whose line is not in the diff at all (or that GitLab still refuses) is posted as a regular MR note quoting path:line, instead of being dropped. Only comments failing both routes count as failures.
  • Softened hard-fail: the job now only fails when nothing at all reached the MR (systemic: revoked token, API down), not when anchors were bad — those degrade to notes.
  • Error surfacing: GitlabApiError.message now includes the response body, so the next report says why GitLab refused.
  • Prompt guardrail: Pass 1/Pass 2 prompts (both GitHub and GitLab terminologies) now state that line must be visible in the diff and that out-of-diff findings should re-anchor to the nearest related changed line.
  • Fallback counts flow through review_post_results.json and render in the sticky tracking note.

If the changes fetch itself fails, posting degrades gracefully to the previous anchor-as-given behavior (plus the note fallback).

Validation

  • bun test: 575 pass, 0 fail (new coverage: buildDiffIndex parsing, context/added/removed anchoring, out-of-diff fallback, note-fallback failure accounting, entrypoint fallback flow, error-body surfacing, tracking-note rendering).
  • bun run typecheck: clean.
  • Prettier: clean on all touched files.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

https://gitlab.com/factory-nizar/droid-review-anchor-e2e/-/merge_requests/1

Closes VAL-14

gitlab-post-review sent `new_line`-only positions for every RIGHT-side
comment, but GitLab requires both line numbers for unchanged context
lines and refuses lines outside the diff entirely, so those findings
400'd and the job hard-failed (seen on a customer MR where both
approved comments targeted untouched code).

- Build a per-file line index from the MR changes and shape each
  position to the line's role: added -> new_line, removed -> old_line,
  context -> both.
- Post comments whose line is not in the diff (or that GitLab refuses)
  as plain MR notes instead of dropping them; only comments failing
  both routes count as failures, and the job only hard-fails when
  nothing reached the MR at all.
- Surface the GitLab error response body in GitlabApiError messages
  ("400 Bad Request" alone was undebuggable from CI logs).
- Track fallback counts in review_post_results.json and the sticky
  tracking note.
- Tell Pass 1/Pass 2 prompts (GitHub + GitLab) that anchors must be
  lines visible in the diff.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@factory-droid

factory-droid Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Droid finished @factory-nizar's task —— View job


Security Review

The GitLab anchoring and fallback improvements look solid and well-tested. A few prompt/docs/tracking strings should be updated to match the new semantics (removed/LEFT anchors and what counts as "failed").

Comment thread src/core/review/prompts/validator.ts
Comment thread src/core/review/tracking/types.ts Outdated
Comment thread src/gitlab/operations/tracking-note.ts Outdated
Comment thread src/entrypoints/gitlab-post-review.ts
…wording

- validator prompt: mention removed/LEFT lines as valid anchors and what
  `side` means for the line anchor
- ReviewPostOutcome.failed doc + tracking note: `failed` now means both
  inline and note fallback failed, not an anchoring refusal
- fallbackNoteBody: use old_path for LEFT-side comments and drop the
  "outside the MR diff" claim (API refusals also land here)

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@factory-nizar
factory-nizar merged commit 4e44f79 into dev Aug 13, 2026
13 checks passed
@factory-nizar
factory-nizar deleted the nizar/gitlab-anchor-fallback branch August 13, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants