* fix(ci): add idempotency check to prevent duplicate review posts on retry
When the batch createReview fails with a 5xx/408/network error, the
request may still have landed on the server. Before retrying per-comment,
the workflow now:
- Tags each review/comment/summary with a per-run HTML comment ID derived
from runId + runAttempt + content hash.
- Queries existing reviews and review comments to detect whether the batch
actually landed, and only retries the comments that are missing.
- Before retrying an individual comment whose request may have reached
GitHub, cools down (honoring rate-limit headers) then checks whether the
comment already exists, treating it as success instead of posting a
duplicate.
- Skips posting the summary comment when one with the same run tag already
exists.
- Adds read-API retry/pacing helpers (withRetry/readWithPacing/readAllPages)
with shorter spacing than writes (OCR_READ_SUCCESS_DELAY /
OCR_READ_LOW_REMAINING_SPACING) since reads are cheaper but still consume
the primary rate limit.
Degrades gracefully to the original fallback (accepting duplicate risk)
when the idempotency read calls themselves fail.
* fix(ci): harden idempotency checks in ocr-review workflow
Address code review findings on the GitHub Actions PR auto-review
workflow (applied to both .github/workflows and examples copies):
- readAllPages: cap pagination at maxPages=50 (default) to prevent
unbounded loops, and validate the argument is a positive integer.
- getPostedCommentIds: anchor the ID regex to the HTML comment wrapper
(<!-- ocr-... -->) with a capture group to avoid false positives from
user-generated content.
- isCommentAlreadyPosted: return null (unknown) instead of false when
the read API fails, so callers do not silently risk duplicates; accept
a postedIdsCache to reuse a single paginated walk across retries.
- hasIssueCommentWithId: return null (unknown) on read API failure, and
match the summary tag with an anchored regex for consistency.
- Call sites: handle null by skipping retry/posting to avoid duplicates
while surfacing the failure in the summary.
* fix(ci): validate env config and document intentional behaviors
Address code review findings on the ocr-review workflow (applied to
both .github/workflows and examples copies):
- parseNonNegInt: add a validation helper for env-var parsing so
negative or non-numeric values (e.g. OCR_MAX_RETRIES=-5) fall back to
defaults instead of bypassing the `|| default` guard (a negative
parseInt result is truthy). All seven retry/pacing config values now
use it.
- readAllPages: document that the 50-page cap is an intentional safety
valve against unbounded loops, not a normal mode; callers that depend
on completeness already degrade safely to null (unknown), so a
truncated walk does not silently produce duplicates.
- commentId: document that the 12-hex-char (48-bit) hash collision
scope is a single PR (listReviewComments is PR-scoped) and a single
run produces only tens to hundreds of comments, making the
birthday-bound collision probability negligible (~1e-7 at 10k).
* docs(github_actions): sync README with retry/idempotency features in ocr-review.yml
- Add OCR_READ_SUCCESS_DELAY and OCR_READ_LOW_REMAINING_SPACING variables
for read API pacing used by the idempotency check
- Document the three GitHub rate-limit retry strategies (primary reset,
retry-after header, secondary no-header backoff)
- Add 'Idempotency: avoiding duplicate review comments' section describing
how the workflow detects already-landed comments via per-run HTML tags
and skips retrying when the read API is unavailable
* fix(ci): use full sha256 hash for review comment idempotency IDs
Drop the .slice(0, 12) truncation in commentId() and use the full 64-char
(256-bit) sha256 hex digest. The truncated 12-char hash carried a tiny but
nonzero collision risk whose failure mode was a silently dropped inline
comment (the idempotency check would mistake two distinct comments for
duplicates). The full hash makes the collision probability effectively
zero with no meaningful downside; the ID regex already used [a-f0-9]+ so
it accepts the longer IDs unchanged.
* fix(ci): use random per-comment IDs and defer body assembly in review workflow
Replace the content-derived commentId() (sha256 of path/line/content) with
a random per-comment ID (crypto.randomBytes) and restructure the inline-
comment flow around an item struct that carries { comment, id, lines }.
This fixes two issues in the idempotency check:
1. ID was recomputed on every failure check. Each inline comment is now
assigned one random ID up front and carried on the item struct, so the
retry/idempotency logic reads item.id directly. The comment body (which
embeds the ID) is assembled only at API-call time in toReviewPayload(),
eliminating repeated hash computation.
2. Content-derived IDs collided for distinct comments sharing the same
path/line/content. A random ID guarantees two such comments get
different IDs, so the idempotency check no longer mistakes the second
for a duplicate of the first and silently drops it on retry.
formatComment/commentId are removed (no callers remain) and replaced with
newCommentId/resolveLines/toReviewPayload/buildBody. The matching regex
already used [a-f0-9]+ so it accepts the new random tokens unchanged.
README ID-format placeholder updated from <hash> to <token>.
* docs(ci): correct misleading readAllPages truncation comment
The comment claimed 'a truncated walk does not silently produce
duplicates' because callers 'degrade safely by returning null on read
failures.' That reasoning only holds when the read API THROWS (rate
limit, 5xx): isCommentAlreadyPosted/hasIssueCommentWithId then return
null (unknown) and the caller skips retrying. A truncated walk does not
throw — it returns a partial set silently, so isCommentAlreadyPosted
returns false (definitively 'not posted') for comments beyond the cap,
and the retry loop reposts them, producing duplicates.
Rewrite the comment to state the cap is an intentional safety valve and
to explicitly distinguish truncation (partial data, can duplicate) from
thrown read failures (null/unknown, safe). No behavior change.
* fix(ci): drop stale postedIdsCache to prevent duplicate inline comments
isCommentAlreadyPosted reused a single listReviewComments snapshot
(postedIdsCache) across all per-comment retries. As comments landed
during the loop, the snapshot went stale; a 5xx-landed comment checked
against the stale snapshot would be reported as 'not posted' and
retried, posting a duplicate.
Remove the cache and walk fresh on every check. The extra reads are
paced via readAllPages/readWithPacing (with retry honoring retry-after
and x-ratelimit-reset) and degrade to null — skip retry — if the read
API ultimately fails, so they cannot produce duplicates. The cache
provided no real benefit in this path: checked comments are either
genuine misses (correctly false) or just-landed (a fresh walk catches
them), so hits essentially never occurred.
* ci: add rate limit handling and version verification to GitHub Actions workflow
- Add version check after OCR installation to verify successful setup
- Implement exponential backoff retry logic for GitHub API rate limits
- Add delays between individual comment posts to avoid secondary rate limits
- GitHub enforces ~80 content-generating requests per minute; spacing calls
helps stay under that threshold with 2-second base delay and up to 3 retries
* ci: refine rate limit handling in GitHub Actions workflow
- Add `|| true` to ocr version check for error isolation
- Narrow rate limit detection to 429 and 403 with rate-limit
message matching, avoiding retries on permission/auth failures
- Extract hardcoded delay constants into env-configurable
variables (OCR_RETRY_BASE_DELAY, OCR_MAX_RETRIES,
OCR_SUCCESS_DELAY, OCR_FAILURE_DELAY) with sensible defaults
- Document optional environment variables in workflow header
* docs: add environment variable configuration guide for retry and delay settings
* ci: add rate-limit resilience and version check to GitLab CI pipeline
- Add `ocr version || true` after install for diagnostic logging
- Add `api_request_with_retry` function with exponential backoff
for 429 and 403 (rate-limit message matching) errors
- Respect GitLab `Retry-After` header when present
- Extract delay constants into CI/CD-configurable variables
(OCR_RETRY_BASE_DELAY, OCR_MAX_RETRIES,
OCR_SUCCESS_DELAY, OCR_FAILURE_DELAY) with defaults
- Add pacing delays between successful/failed discussion posts
- Document optional CI/CD variables in pipeline header comments
* docs: add retry/delay settings section to GitLab CI README
* ci: fix rate-limit retry delay exhaustion handling in CI workflows
- GitHub Actions: distinguish exhausted rate-limit retries from other errors, apply SUCCESS_DELAY (2s) instead of FAILURE_DELAY (1s) when retries exhausted
- GitLab CI: return structured result from api_request_with_retry to differentiate failure types, apply context-aware delays based on rate-limit exhaustion status
- Both: prevent perpetuating rate-limit failures by using longer delays after retry exhaustion
* ci: align GitHub Actions rate-limit retry with header-based strategy
Derive wait durations from response headers (retry-after, x-ratelimit-reset)
instead of fixed exponential backoff, add proactive throttle when remaining
quota is low, honor batch-level rate limits before per-comment retry, and add
support for transient 5xx/408 errors.
* feat(gitlab-ci): enhance rate-limit handling with jitter, max retry delay, and proactive throttling
- Add ±25% jitter on retry delays to avoid thundering herd problems
- Add OCR_MAX_RETRY_DELAY (default 60s) to cap per-retry wait time
- Add OCR_RATE_LIMIT_THRESHOLD (default 10) for proactive throttling
based on GitLab RateLimit-Remaining response header
- Parse Retry-After header properly (handle non-numeric values)
- Apply retry logic to all API requests (notes, versions, discussions)
- Parse and log RateLimit-Remaining/Limit headers for observability
- Double pacing delay when remaining quota drops below threshold
- Update README with new configuration variables and behavior docs
* fix(examples): sync rate-limit docs with script defaults and add missing variables
- GitHub Actions README: fix OCR_RETRY_BASE_DELAY default from 2000 to 60000
(matching script code and header comments)
- GitHub Actions README: add missing OCR_RETRY_MAX_DELAY, OCR_LOW_REMAINING_THRESHOLD,
OCR_LOW_REMAINING_SPACING variables
- GitHub Actions README: add GitHub Rate Limits doc reference link
- GitHub Actions yml header: add OCR_LLM_USE_ANTHROPIC and llm.extra_body notes
- GitLab CI yml header: add llm.extra_body note
- GitLab CI README: add GitLab Rate Limits doc reference link
* ci(examples): unify header lookup and add transient retry backoff
- GitHub Actions: extract inline header closure into a reusable getHeader
helper; use it in both computeRetryDelayMs and logRateLimitQuota for
consistent case-insensitive header access.
- GitHub Actions: use a 2s transientBase for 5xx/408 exponential backoff
instead of the 60s rate-limit base, since server hiccups are typically
short-lived and the longer base stalled CI jobs unnecessarily.
- GitLab CI: add a _get_header helper and route all header access
(Retry-After, RateLimit-*) through it, matching the GitHub Actions
approach.
- GitLab CI: add transient retry logic for 5xx/408 errors with a 2s
base delay, so server errors no longer fail immediately.
* docs(examples): add concurrency control to CI workflow examples
- GitHub Actions: add concurrency group with cancel-in-progress to avoid
redundant review runs on rapid pushes
- GitLab CI: add interruptible and resource_group to cancel outdated
review jobs when new commits are pushed to the same MR
* docs(examples): improve GitLab CI example with fork MR and concurrency support
- Support forked MR pipelines by using CI_COMMIT_SHA as --to target
- Fall back to CI_JOB_TOKEN when GITLAB_API_TOKEN is unavailable
- Use appropriate auth header (JOB-TOKEN vs PRIVATE-TOKEN) based on token source
- Add --audience agent flag for machine-consumable review output
- Make diff_refs required for inline comments, simplify post_discussion signature
- Improve summary with inline vs fallback comment breakdown
- Add documentation comments for fork MR setup requirements
* docs(examples): use pull_request_target and SHA refs for fork PR support
- Switch trigger from pull_request to pull_request_target so secrets
are available for PRs from forks
- Use head SHA instead of branch ref for checkout and ocr --to, since
fork branches don't exist on the origin remote
- Add explicit fetch step to ensure fork commits are available
- Update condition checks and comments to reflect the new event name
* docs: sync READMEs with CI script changes for fork PR/MR support
* chore: remove --audience agent flag and simplify JSON parsing in CI examples
- Remove --audience agent flag from ocr review commands in CI examples
- Simplify JSON output parsing by reading directly without skipping first line
- Update README docs to reflect the simplified CLI usage
* fix: correct typo in .gitlab-ci.yml comment
Change 'confuring' to 'setting' in CI variable configuration comment.
* docs: add CI/CD integration section and examples
- Add CI/CD Integration section to README.md and README.zh-CN.md
- Add GitHub Actions workflow example (examples/github_actions/)
- Add GitLab CI pipeline example (examples/gitlab_ci/)
- Add examples README with overview of integration options
* feat(examples): enhance GitHub Actions demo with comment trigger and improved error handling
- Add issue_comment event trigger with /open-code-review and @open-code-review keywords
- Add PR context resolution for comment-triggered events via GitHub API
- Improve ref handling to support both PR events and comment events
- Add individual comment fallback with retry when batch review fails
- Add posting statistics (success/failed counts) to summary comment
- Update README with comment trigger flow and customization guide
* docs(examples): add --background flag usage guide for GitHub Actions and GitLab CI
Explain how to pass PR/MR title as background context to help OCR
provide more relevant and context-aware review comments.
* feat(examples): simplify PR trigger and add skip-existing-review guide for GitLab CI
- Reduce GitHub Actions PR trigger to 'opened' only (avoid redundant
reviews on synchronize/reopened events)
- Add GitLab CI documentation for checking existing OCR comments before
running review to save LLM tokens