## TL;DR

The agent retried a review submission after a timeout, and both attempts landed. Make review submission idempotent: generate one idempotency key per review and dedupe on it, or check for an existing identical review before posting.

## Error

```text
PR #520 shows two identical reviews from review-agent[bot], posted 40 seconds apart.
```

## Steps

1. Confirm the duplication: the two reviews have identical bodies and close timestamps. Expected: a retry, not two separate analyses.
2. Delete the duplicate review. Expected: one review remains.
3. Add idempotency: create a key from (PR number, head SHA, review content hash) and store it; skip posting if the key was already submitted. Expected: retries never create a second review.
4. Alternatively, list existing reviews before posting and skip when an identical body already exists. Expected: the same dedupe without extra storage.

## When to use

- The same agent review appears twice on one PR.
- The agent retries on timeouts or errors.

## When not to use

- Two different reviews (different content)  -  that is intended behavior, not duplication.
- A human double-submitted; this fix targets the agent's retry logic.

## Tool compatibility

- GitHub / GitLab review APIs.
- Any review agent with retries.

## Variant phrasings

### Double-posted inline comments

Same fix at comment level: dedupe by (file, line, body hash).

### Duplicate reviews after a crash-restart

Persist the submitted-review keys so restarts do not resubmit.

## Why it happens

Review submission is not idempotent server-side: a retried POST after a timeout creates a second review because the first one actually succeeded. Without a client-side key, at-least-once delivery means duplicate reviews.

## Edge cases

- Content-hash dedupe must normalize whitespace, or trivial formatting differences defeat it.
- Keep the idempotency window bounded (e.g. per head SHA) so legitimate re-reviews after new commits still post.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_8uv4BFZd2C8cHy_krxIpAQ
