Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ inputs:
Value in (0, 1]; ignored unless incremental is true.
required: false
default: '0.6'
resolve_outdated:
description: >-
Resolve the action's own outdated inline review threads (threads GitHub
has already marked outdated because their lines no longer exist in the
diff). Threads a human replied to, threads already resolved, and threads
whose lines a finding from the current run still covers are never touched.
One of: 'false' (default, does nothing and makes no API calls), 'report'
(log what would be resolved, change nothing — run this first), 'true'
(actually resolve). 'true' requires the calling workflow to grant
`contents: write` in its own `permissions:` block; the default read-only
token cannot resolve a thread and the action will warn and continue.
Overlap with current findings is decided by incremental_overlap_threshold.
required: false
default: 'false'
review_comment_batch_size:
description: >-
Maximum number of inline comments packed into a single createReview call.
Expand Down Expand Up @@ -151,6 +165,16 @@ outputs:
comments_failed:
description: Number of inline comments that failed to post.
value: ${{ steps.post.outputs.comments_failed }}
comments_resolved:
description: >-
Number of outdated bot review threads resolved this run. Always 0 unless
resolve_outdated is 'true'.
value: ${{ steps.post.outputs.comments_resolved }}
comments_resolved_preview:
description: >-
Number of threads resolve_outdated 'report' mode would have resolved.
Always 0 in 'false' and 'true' modes.
value: ${{ steps.post.outputs.comments_resolved_preview }}
summary_comment_url:
description: URL of the posted/updated summary comment, if any.
value: ${{ steps.post.outputs.summary_comment_url }}
Expand Down Expand Up @@ -316,6 +340,7 @@ runs:
OCR_REVIEW_COMMENT_BATCH_SIZE: ${{ inputs.review_comment_batch_size }}
OCR_ROUTE_SEVERITY_BELOW: ${{ inputs.route_severity_below }}
OCR_ROUTE_CATEGORIES: ${{ inputs.route_categories }}
OCR_RESOLVE_OUTDATED: ${{ inputs.resolve_outdated }}
with:
github-token: ${{ inputs.github_token }}
script: |
Expand Down Expand Up @@ -346,4 +371,5 @@ runs:
reviewCommentBatchSize: parseInt(process.env.OCR_REVIEW_COMMENT_BATCH_SIZE, 10),
routeSeverityBelow: process.env.OCR_ROUTE_SEVERITY_BELOW,
routeCategories: process.env.OCR_ROUTE_CATEGORIES,
resolveOutdated: process.env.OCR_RESOLVE_OUTDATED,
});
37 changes: 36 additions & 1 deletion examples/github_actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ The action posts a summary issue comment plus inline review comments. Two inputs
|-------|---------|-------------|
| `sticky_summary` | `'true'` | Update an existing summary comment in place instead of posting a new one each run. |
| `incremental` | `'false'` | Only append inline comments whose `(path, line range)` does not overlap an existing bot review comment. History is never deleted (non-destructive). |
| `incremental_overlap_threshold` | `'0.6'` | IoU threshold `incremental` uses to decide whether a multi-line comment overlaps an existing one. Two single-line comments match on the same line; single- vs multi-line never match. Ignored unless `incremental` is `'true'`. |
| `incremental_overlap_threshold` | `'0.6'` | IoU threshold `incremental` uses to decide whether a multi-line comment overlaps an existing one. Two single-line comments match on the same line; single- vs multi-line never match. Shared with `resolve_outdated`, which uses the same test to decide whether a current finding still covers an outdated thread. Ignored unless `incremental` or `resolve_outdated` is enabled. |
| `resolve_outdated` | `'false'` | Resolve the action's own outdated inline threads. `'false'` does nothing; `'report'` logs what it would resolve; `'true'` resolves them. See below. |

```yaml
- uses: alibaba/open-code-review@main
Expand All @@ -159,6 +160,40 @@ The action posts a summary issue comment plus inline review comments. Two inputs

> `sticky_summary` and `incremental` must be quoted strings (`'true'`/`'false'`); the action compares them as strings, so an unquoted YAML boolean will not match.

### Resolve outdated review threads

Over a long PR, the bot's inline comments pile up on code that no longer exists. `resolve_outdated` closes those threads, using GitHub's own `isOutdated` flag — no LLM is involved in the decision, and the action only ever touches threads GitHub has already marked outdated.

| Value | Behavior |
|-------|----------|
| `'false'` (default) | Does nothing. No GraphQL calls are made at all. |
| `'report'` | Lists what would be resolved, and why each other thread was skipped. Changes nothing. |
| `'true'` | Resolves them. |

Start with `'report'` for a few PRs and read the `[resolve-outdated]` log line before switching to `'true'`.

```yaml
permissions:
contents: write # required by resolve_outdated: 'true'
pull-requests: write

steps:
- uses: alibaba/open-code-review@main
with:
resolve_outdated: 'report'
```

A thread is left alone unless **the action created it**. Ownership is decided by the marker OCR writes into every inline comment it posts, not by the comment's author — under the default `GITHUB_TOKEN` every workflow in your repository posts as `github-actions[bot]`, so a sibling workflow's review threads would otherwise be indistinguishable from OCR's own. Beyond that, a thread is left alone whenever **a human has replied** to it, when it is already resolved, when a finding from the current run still covers its lines, or when it has more comments than one API page returns (so a reply the action cannot see is never resolved over). Resolution also runs only after a run that actually produced findings: a run that failed to parse OCR's output, or that reported nothing, resolves nothing — "the model said nothing this time" is not evidence the old findings are gone. At most 50 threads are resolved per run; the rest carry over to the next one.

Outputs: `comments_resolved` (threads resolved, `'true'` mode) and `comments_resolved_preview` (threads that would be resolved, `'report'` mode). Both are always present.

Four things worth knowing before you turn this on:

- **`'true'` requires `contents: write`.** The default read-only `GITHUB_TOKEN` cannot resolve a thread; the calling workflow must grant `contents: write` in its own `permissions:` block. Without it the action logs a warning naming the missing permission and continues — the review itself still posts.
- **A force-push can mark a live finding's thread outdated.** GitHub sets `isOutdated` when the thread's lines are no longer in the diff, and it stays set even after a force-push that lands identical content, so a rebase can outdate a thread whose finding is still real. The current-run overlap check catches this when the model re-reports the finding; if it doesn't, the thread closes while the problem remains. This is the main reason to run `'report'` first.
- **It only does anything on re-runs.** A thread can only become outdated after a later push, so the feature is exercised only by workflows that review on update (`types: [opened, synchronize, reopened]`, as in the sample workflow above). This repository's own review workflow triggers on `opened` only and never reaches the resolution path — do not read its runs as evidence the feature works for you.
- **Resolving is not deleting.** Resolved threads collapse but stay readable, and anyone can unresolve one.

### Adjust retry and delay settings

When posting review comments individually (fallback mode), the action honors GitHub rate-limit headers (`retry-after`, `x-ratelimit-*`) with exponential backoff. The retry strategy follows GitHub's documented guidance for REST API rate limits — see [Rate limits for the REST API](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2026-03-10) for details on primary/secondary rate limits and recommended retry behavior:
Expand Down
Loading
Loading