Skip to content

fix(review): stop re-reviewing unchanged heads after duplicate checks - #970

Merged
steipete merged 1 commit into
openclaw:mainfrom
PollyBot13:fix/issue-967-check-cache-churn
Jul 31, 2026
Merged

fix(review): stop re-reviewing unchanged heads after duplicate checks#970
steipete merged 1 commit into
openclaw:mainfrom
PollyBot13:fix/issue-967-check-cache-churn

Conversation

@PollyBot13

@PollyBot13 PollyBot13 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Closes #967

What Problem This Solves

Fixes an issue where contributors with an unchanged pull-request head could receive repeated full ClawSweeper reviews after equivalent GitHub Actions check runs accumulated on that head. Those unnecessary cycles consume review capacity and can expose contributors to different model verdicts despite no change to their code or discussion.

Why This Change Was Made

Cache-key construction now de-duplicates equivalent compacted check-run tuples across the structural, semantic, and content review caches. The full check context supplied to the reviewer is deliberately unchanged; distinct names, apps, statuses, or conclusions still invalidate the relevant caches, and existing completeness/truncation safeguards remain intact.

This keeps the repair at the cache identity boundary requested in #967 rather than introducing a workflow-name allowlist or hiding repeated runs from review context.

User Impact

An unchanged PR head no longer receives another full review solely because automation appended another equivalent check run. Meaningful CI changes—such as a previously successful check failing—continue to trigger fresh evaluation.

Evidence

  • Added a regression proving one additional {name,status,conclusion,app} duplicate leaves the content digest unchanged.
  • Added an end-to-end cache-predicate regression proving the unchanged head is reused instead of sent through another full review.
  • Added semantic-cache coverage and updated the structural-cache source assertion so all three cache layers use the same normalization boundary.
  • Added contributor-suggested identity regressions proving runs that differ only by name or app remain distinct, and that one failure among several repeated runs invalidates the cache.
  • Mutation proof: narrowing the identity to name-only makes exactly those two guard tests fail (38/40); restoring full-object identity returns the content-cache suite to 40/40.
  • The new regression failed on unmodified main before the fix: the two equivalent contexts produced different content digests.
  • Rebased onto current main at c8f44886fde43c44187a2f239dc0623fdaa279de.
  • pnpm run check:static, pnpm run build:all, and pnpm run lint pass on Node 26.5.0 / pnpm 11.10.0.
  • 148 focused content, semantic, structural, and review-flow tests pass.
  • pnpm run check completes all changed-surface validation; its five remaining failures are in repair files unchanged from origin/main and are Linux Landlock/capability tests running on macOS (capset is absent from Darwin libc). Hosted Linux CI is the authoritative full-suite result.
  • Codex review of both the uncommitted change and committed branch found no actionable defects.

Real Behavior Proof

Claim: Adding another equivalent compacted check run to an unchanged PR head preserves the review cache identity, while a distinct failure still invalidates it.

Exercised surface: The production itemContentDigestForTest export and reviewContentCacheHit predicate from the built package.

Scenario: The fixture keeps the PR source revision, head, base, files, timeline, and check outcome fixed. It compares one successful notify run with two byte-equivalent successful notify runs, then changes the second run's conclusion to failure.

Command and environment: A Node 26.5.0 ESM harness against pnpm run build output on macOS 15.

Observed result:

{
  "priorDigest": "aadae6eeaf1ce3ecfc7c0ef8eeeb72b8d1c18c6c693ff9fd38268154e260cbf5",
  "repeatedDigest": "aadae6eeaf1ce3ecfc7c0ef8eeeb72b8d1c18c6c693ff9fd38268154e260cbf5",
  "duplicateStable": true,
  "distinctFailureInvalidates": true,
  "cacheHit": true
}

Trace: The executable fixture is captured as the focused regression in test/review-content-cache.test.ts; semantic identity is independently covered in test/review-semantic-cache.test.ts.

Limits: This does not alter check-run collection, the reviewer-visible context, the 100-run completeness boundary, scheduler cadence, explicit re-review commands, or maintainer-request behavior.

AI Assistance

This PR was implemented and reviewed with OpenAI Codex assistance. The author ran and inspected the reported validation and remains responsible for the change.

@PollyBot13

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 automation 🚨 Merging this PR could break CI, automerge, proof capture, label sync, or automation. labels Jul 30, 2026
@clawsweeper

clawsweeper Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 31, 2026, 6:01 AM ET / 10:01 UTC.

ClawSweeper review

What this changes

The PR de-duplicates byte-identical compacted pull-request check runs before structural, semantic, and content cache identities are calculated, preventing repeated equivalent checks from forcing another full review.

Merge readiness

⚠️ Ready for maintainer review - 2 items remain

Keep open for maintainer review. This is a focused repair for a source-reproducible cache-churn bug, and the updated branch applies the shared normalization at all three review-cache boundaries while preserving distinct check-run outcomes; no concrete patch defect was found.

Priority: P2
Reviewed head: 0a3d5c55a2bd0147b9aa5a1ea8e26434a728ed9b

Review scores

Measure Result What it means
Overall readiness 🦞 diamond lobster (5/6) A focused, well-guarded automation fix with direct after-fix built-package proof and no actionable correctness finding.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): The PR body supplies after-fix built-package live output showing equal digests and a cache hit for an equivalent duplicate run, plus invalidation for a changed failure conclusion; this directly exercises the production digest and cache predicate.
Patch quality 🦞 diamond lobster (5/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (live_output): The PR body supplies after-fix built-package live output showing equal digests and a cache hit for an equivalent duplicate run, plus invalidation for a changed failure conclusion; this directly exercises the production digest and cache predicate.
Evidence reviewed 5 items Shared cache-key normalization: The branch introduces one helper that removes only byte-identical compacted check-run objects, then calls it from the structural, semantic, and content cache-key paths. Runs differing by name, app, status, or conclusion remain separate cache inputs.
All three cache layers use the same boundary: The PR replaces raw pull-check hashing in the structural and content paths and raw semantic-context inclusion with the shared normalization helper, matching the issue’s stated scope rather than changing check collection or reviewer context.
Regression coverage preserves meaningful CI changes: Focused tests cover duplicate stability, distinct name/app identity, a newly failing repeated run, content-cache reuse, semantic-cache reuse, and the structural source call site. The contributor also incorporated the two identity guardrails proposed in the related discussion.
Findings None None.
Security None None.

How this fits together

ClawSweeper collects GitHub pull-request check state alongside source, discussion, and review metadata to decide whether an existing review can be reused. This change normalizes only the check-state input to the three cache keys; the reviewer-visible check context and collection completeness rules continue downstream unchanged.

flowchart LR
  A[GitHub pull-request check runs] --> B[Compacted check state]
  B --> C[Duplicate-run normalization]
  C --> D[Structural cache key]
  C --> E[Semantic cache key]
  C --> F[Content cache key]
  D --> G[Reuse or refresh review]
  E --> G
  F --> G
Loading

Before merge

  • Resolve merge risk (P1) - Merging intentionally changes three persisted cache identities, so previously stored records may miss once after deployment before subsequent equivalent check state can reuse the normalized keys.
  • Resolve merge risk (P1) - The normalization treats identical compacted runs as one signal; this is appropriate for the documented cache loop, but any future removal of a distinguishing field from compacted check runs could broaden coalescing and should retain focused identity regressions.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Cache layers normalized 3 cache identities changed Structural, semantic, and content reuse now share one duplicate-check boundary, avoiding a partial fix that still churns earlier cache gates.
Focused regression coverage 153 lines added across 3 test files The patch covers duplicate stability and preserves distinct names, apps, and failures rather than testing only the happy path.
Patch scope 6 files; 179 added, 4 removed The implementation is small and contained to cache-key construction plus focused assertions.

Merge-risk options

Maintainer options:

  1. Land with post-deploy cache observation (recommended)
    Accept the intentional one-time cache-key transition and verify that duplicate completed runs stop causing repeat full reviews while a changed conclusion still triggers refresh.
  2. Pause for an explicit cache-version migration
    Defer this PR if maintainers want the one-time invalidation of structural, semantic, and content cache identities to be represented by an explicit version or migration policy first.

Technical review

Best possible solution:

Land the narrow shared cache-key normalization with its identity guardrails, then observe one post-deploy cache refresh and confirm that equivalent repeated check runs no longer schedule full re-reviews while changed conclusions still do.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible with medium confidence: the documented built-package harness compares equivalent duplicate check-run inputs against the production digest and cache predicate, and the pre-fix raw cache-key paths shown by the PR explain the miss on current main. This read-only review did not execute the harness itself.

Is this the best way to solve the issue?

Yes. Normalizing only the cache identity is the narrowest maintainable repair because it preserves full check context for reviewers, keeps truncation/completeness behavior intact, and retains distinct check names, apps, statuses, and conclusions as invalidating signals.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against c8f44886fde4.

Labels

Label changes:

  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.

Label justifications:

  • P2: This fixes recurring but bounded review-automation churn and reduces unnecessary full review cycles without affecting end-user message delivery or core runtime availability.
  • merge-risk: 🚨 automation: The PR changes the identity inputs that decide whether review automation skips or performs a fresh review, including persisted cache behavior across deployment.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body supplies after-fix built-package live output showing equal digests and a cache hit for an equivalent duplicate run, plus invalidation for a changed failure conclusion; this directly exercises the production digest and cache predicate.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body supplies after-fix built-package live output showing equal digests and a cache hit for an equivalent duplicate run, plus invalidation for a changed failure conclusion; this directly exercises the production digest and cache predicate.

Evidence

What I checked:

  • Shared cache-key normalization: The branch introduces one helper that removes only byte-identical compacted check-run objects, then calls it from the structural, semantic, and content cache-key paths. Runs differing by name, app, status, or conclusion remain separate cache inputs. (src/review-checks-digest.ts:3, 0a3d5c55a2bd)
  • All three cache layers use the same boundary: The PR replaces raw pull-check hashing in the structural and content paths and raw semantic-context inclusion with the shared normalization helper, matching the issue’s stated scope rather than changing check collection or reviewer context. (src/clawsweeper.ts:3185, 0a3d5c55a2bd)
  • Regression coverage preserves meaningful CI changes: Focused tests cover duplicate stability, distinct name/app identity, a newly failing repeated run, content-cache reuse, semantic-cache reuse, and the structural source call site. The contributor also incorporated the two identity guardrails proposed in the related discussion. (test/review-content-cache.test.ts:297, 0a3d5c55a2bd)
  • Concrete current-main reproduction path: The PR documents that the production digest and cache predicate produce different content digests for equivalent duplicate runs on unmodified main, while the patched built-package harness reports an unchanged digest and a cache hit. This supports the reported cache-loop mechanism, although this read-only review did not execute the harness. (test/review-semantic-cache.test.ts:1082, 0a3d5c55a2bd)
  • Related work selected this branch as the broader repair: The reporter for the canonical issue reviewed the earlier narrow PR, identified two missing identity guards, and stated that this PR has the better shared three-cache boundary; the earlier PR was then closed unmerged. (0a3d5c55a2bd)

Likely related people:

  • masatohoshino: Reported the cache-loop mechanism in the canonical issue, reviewed the proposed identity boundary, and supplied the two guard tests that the current branch adopted. (role: related-problem reporter and review contributor; confidence: medium; files: test/review-content-cache.test.ts, src/review-checks-digest.ts)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (4 earlier review cycles)
  • reviewed 2026-07-30T18:31:48.440Z sha db44c8a :: needs maintainer review before merge. :: none
  • reviewed 2026-07-30T21:42:26.089Z sha db44c8a :: needs maintainer review before merge. :: none
  • reviewed 2026-07-30T23:03:39.468Z sha db44c8a :: needs maintainer review before merge. :: none
  • reviewed 2026-07-31T00:26:41.366Z sha db44c8a :: needs maintainer review before merge. :: none

@masatohoshino

Copy link
Copy Markdown
Contributor

I filed #967 and had a narrower version of this in flight; yours is the better boundary, so I closed mine. Two tests I had that yours doesn't cover, if they're useful — both guard the identity key rather than the de-duplication itself.

I checked them against your branch at db44c8a0: they pass as-is (41 tests, 41 passing).

To confirm they earn their place, I narrowed your identity key to name-only:

-    const identity = stableJsonCodeUnit(checkRun);
+    const identity = stableJsonCodeUnit((checkRun as Record<string, unknown>)?.name);
✖ content digest keeps check runs that differ only in name or app
✖ content digest busts when one of several repeated check runs newly fails
ℹ tests 41   ℹ pass 39   ℹ fail 2

Your three current tests still pass under that change — a regression that widens what counts as "the same run" lands silently without these two.

test("content digest keeps check runs that differ only in name or app", () => {
  const pull = item({ kind: "pull_request", number: 200 });
  const check = { name: "smoke", status: "completed", conclusion: "success", app: "github-actions" };
  const digest = (checkRuns: unknown[]) =>
    itemContentDigestForTest(
      pull,
      pullContext({
        pullChecks: {
          complete: true,
          checkRuns,
          checkRunsTruncated: false,
          statuses: [],
          statusesTruncated: false,
        },
      }),
    );

  const alone = digest([check]);
  const byName = digest([check, { ...check, name: "smoke (windows)" }]);
  const byApp = digest([check, { ...check, app: "blacksmith-sh" }]);

  assert.notEqual(alone, byName);
  assert.notEqual(alone, byApp);
  assert.notEqual(byName, byApp);
});

test("content digest busts when one of several repeated check runs newly fails", () => {
  const pull = item({ kind: "pull_request", number: 200 });
  const notify = {
    name: "notify",
    status: "completed",
    conclusion: "success",
    app: "github-actions",
  };
  const digest = (checkRuns: unknown[]) =>
    itemContentDigestForTest(
      pull,
      pullContext({
        pullChecks: {
          complete: true,
          checkRuns,
          checkRunsTruncated: false,
          statuses: [],
          statusesTruncated: false,
        },
      }),
    );

  const allPassing = digest([notify, notify, notify]);
  const oneFailing = digest([notify, notify, { ...notify, conclusion: "failure" }]);

  assert.notEqual(allPassing, oneFailing);
});

The second one matters for the case that started this: the head I measured carried 13 identical notify runs, so "several repeats, one of them newly failing" is the realistic shape rather than a single duplicated pair.

I also wrote a third, asserting a checks payload with no checkRuns array is passed through. I dropped it — I could not construct a change to reviewPullChecksDigestParts that it catches, so it looked like coverage without teeth. Mentioning it only so you know that branch is untested if you'd rather pin it some other way.

One thing your version handles that mine didn't: extracting the helper into a shared module so all three cache identities can use it. I kept mine to the content digest specifically to avoid that relocation, which left the semantic and structural digests still churning. That was the wrong call.

Take or ignore these freely — no need to credit.

@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. labels Jul 30, 2026
@PollyBot13
PollyBot13 force-pushed the fix/issue-967-check-cache-churn branch from db44c8a to 0a3d5c5 Compare July 31, 2026 09:57
@PollyBot13

Copy link
Copy Markdown
Contributor Author

Thank you — both tests are excellent guardrails, and I added them in 0a3d5c55a2.

I repeated your mutation proof locally: narrowing the identity to name-only makes exactly those two tests fail (38/40), while restoring the full-object identity returns the content-cache suite to 40/40. I agree the non-list pass-through case did not earn a test without a mutation it could catch.

I also rebased the branch onto current main; the broader focused suite is now 148/148. Thanks for the careful review and for steering this toward the shared three-cache boundary.

@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jul 31, 2026
@steipete
steipete merged commit 12af8b7 into openclaw:main Jul 31, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 automation 🚨 Merging this PR could break CI, automerge, proof capture, label sync, or automation. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClawSweeper's own review writes expire the review content cache, so unchanged heads are re-reviewed in a loop

3 participants