Skip to content

fix(review): post fresh comment on re-review instead of silent PATCH - #19

Closed
Koan-Bot wants to merge 20 commits into
mainfrom
koan.atoomic/fix-review-re-post
Closed

Koan-Bot wants to merge 20 commits into
mainfrom
koan.atoomic/fix-review-re-post

Conversation

@Koan-Bot

@Koan-Bot Koan-Bot commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

What

On re-review after new commits, post a new GitHub comment instead of silently PATCHing the existing one.

Why

GitHub does not send notifications when a comment is edited via the API. When the review runner PATCHed the existing summary comment on re-review, the reviewer never received a notification — making it look like the review was never posted. This is the "protection that is too strict" the user observed: the review runs fully but the result is invisible because the PATCH update is silent.

How

  • In run_review(), after the SHA check passes (new commits detected), compare current_shas against prior_shas. When they differ and an existing comment exists, force a fresh POST instead of PATCH.
  • The old review comment is collapsed to a short "Previous review — superseded" notice via _collapse_old_review(), keeping the PR timeline clean.
  • Same-SHA re-reviews (retries, no new code) still skip as before — no behavioral change there.

Testing

  • test_re_review_posts_new_comment_not_patch: verifies fresh POST + old comment collapse
  • test_same_shas_patches_existing_comment: verifies skip on identical SHAs still works
  • TestCollapseOldReview: unit tests for the collapse helper (success, failure isolation, missing id)
  • Full suite: 15355 passed (1 pre-existing failure unrelated to this change)

🤖 Generated with Claude Code

Koan-Bot and others added 20 commits June 3, 2026 11:53
The review comment footer now links "Kōan" to https://koan.anantys.com
and shows the CLI provider and model used (e.g., "Claude · model
claude-opus-4-6"), giving reviewers transparency on how the review
was generated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move provider-specific behavior out of shared CLI code and into the
provider objects so cli_exec no longer special-cases provider names:

- Providers declare stdin prompt-passing support and own the
  argv->stdin rewrite (Claude "-p" placeholder, Codex "exec -").
- Providers can opt into serialized CLI invocations via a process-wide
  file lock. Codex uses it to avoid token-refresh races between
  preflight probes, message formatting, and long review sessions.
- Providers own final-message-file arg placement (Codex inserts the
  flag before the prompt) and auth-failure detection. Codex recognizes
  401 and refresh-token-reuse errors in both stderr and streamed JSONL
  events.

Detect Codex auth failures from stdout-safe runtime lines so a bare 401
stream failure pauses for auth and requeues the active mission instead
of marking it failed. Keep the trusted stdout surface narrow ([cli]
summaries, CLI failure lines, and structured JSON error events) to
avoid false positives from quoted CI logs or source text.

Fix a missing contextlib import that crashed the JSON-event branch of
the runtime auth scan with NameError, and add regression coverage for
both the JSON-event auth case and a benign event that merely quotes a
401 string.

Consolidate the triplicated provider error-event-type set into a single
PROVIDER_ERROR_EVENT_TYPES constant in provider/base.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a post-mission test that feeds a Codex "event_msg" token_count
JSONL event through run_post_mission and asserts both the budget state
and the cost-tracker rows. It verifies that cached_input_tokens are
treated as cache-read and subtracted from counted input, that the
session and weekly token totals reflect billable input plus output,
that the run counter increments, and that the emitted usage row carries
the model, mode, and cache-read counts.

Document the recognized Codex JSONL usage events in
docs/providers/codex.md: "turn.completed" events with a "usage" object
and "event_msg" rollout events whose "payload.type" is "token_count",
along with the cache-read subtraction rule and the latest-snapshot-wins
behavior when multiple usage-bearing events are present.

This is test and documentation only; no production behavior changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consolidate all Kōan footer generation into a single pr_footer.py
module with build_koan_footer() and strip_legacy_footers(). Every
caller (pr_quality, review_runner, plan_runner, fix_runner,
implement_runner, incident_runner, deepplan_runner, claudemd_refresh,
tracker_comment_format) now uses the shared helper, producing a
consistent branded link to koan.anantys.com.

The quality pipeline also strips duplicate/legacy footers (including
"🤖 Generated with Claude Code") from PR bodies before appending
the quality report section.
Add thread-aware filtering to the review comment reply system. When the
bot is the last poster in a thread and no human followed up, the thread
is excluded from the repliable list on subsequent review runs. Also add
a configurable max_thread_depth (default 5) to cap thread length.

New config option: review_reply.max_thread_depth

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
increment_retry_count() and clear_retry_count() did unlocked
load-increment-save, allowing concurrent parallel-session missions to
read the same counter, both compute +1, and one overwrite the other —
under-counting retries by one per race.

Replaces the private _load/_save_retry_tracker helpers with
locked_json_modify / locked_json_read from locked_file.py (the same
pattern used by burn_rate, check_tracker, etc.).  Reads in
get_retry_count and get_retry_info now use a shared lock; writes hold
an exclusive lock across the full mutate cycle.  OSError tolerance on
save is preserved.

Adds a concurrent-increment test that reliably reproduced the race (30
threads all stagnating simultaneously → final count was 1 before fix,
30 after).

Closes Anantys-oss#1701

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_read_session_pct_and_reset now returns a 3-tuple (session_pct,
minutes_until_reset, state) so callers can reuse the already-parsed
dict. _maybe_warn_burn_rate unpacks and uses that dict for the
session_start comparison instead of issuing a second read_text() call.

This removes the window where the run loop could flush a new session
start between the two reads, causing an inconsistent comparison that
silently suppressed the burn-rate alert for an entire new quota cycle.
Also eliminates one redundant file read per iteration when last_warned
is set.

Closes Anantys-oss#1703

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracted a _read_tail(stdout_file, lines) helper that centralises the
seek-to-end / read / size-guard logic.  _tail_hash and classify_stagnation
now delegate to it.  _sample_once reads the larger classification window
(100 lines) once per call; on the abort path it derives the hash from the
last sample_lines of that same buffer and passes the buffer directly to
_classify_from_bytes — eliminating the independent classify_stagnation
file open that raced against the still-writing subprocess.

Added _classify_from_bytes (extracted from classify_stagnation) and two
new tests in TestSingleReadOnAbort that assert exactly one _read_tail
call per _sample_once() invocation, including on the abort path.

Closes Anantys-oss#1702

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…KILL.md

The lite YAML parser doesn't interpret Python unicode escapes, so
emoji fields like "\U0001F50E" were rendered as literal text in Telegram.
Fix all 4 affected skills (check_need, spec_audit, doc, alias), strip
unnecessary quotes from autoreview, and add quote-stripping to
_parse_yaml_lite to prevent this bug class.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A complete, useful review that omits a sentinel-defaultable field
(review_summary.checklist, file_comments[].code_snippet, finding_ref,
or lgtm) was rejected wholesale by validate_review and replaced with
the "⚠️ could not be formatted" placeholder — even though the content
was perfectly usable. The retry didn't help because the model was
consistently terse (observed on esphome/device-builder#1178: two opus
passes, no reflection, fallback posted).

_normalize_review_data() now fills these unambiguous sentinels in place
before validation: checklist→[], code_snippet→"", finding_ref→"", and
lgtm derived from finding severities. It deliberately does NOT fabricate
semantically meaningful fields (summary, comment, title, severity, line
numbers, checklist item/passed) — genuinely incomplete reviews still
fail validation and fall back as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New core skill that takes a PR URL and produces a pedagogical explanation:
- What problem the PR solves (with concrete examples)
- How the fix works, step by step
- The data flow after the change
- Whether a simpler approach could have worked

Includes handler for Telegram bridge, runner with Claude CLI invocation,
dispatch registration with /xp alias, mission dedup support, and 22 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Headless agent commits (rebase, squash, claudemd refresh) ran target-repo
pre-commit hooks. device-builder-frontend's .husky pre-commit runs
'npm run format:check && npm run lint && npm test', which exceeds the 60s
git timeout (npm test can also hang on watch mode), raising an unhandled
TimeoutExpired that crashes the whole rebase (PR 609).

Add --no-verify to commit_if_changes, squash, and claudemd refresh. CI
validates the PR; local hooks are not Kōan's gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two fixes for the regression where reviews post duplicate comments:

1. Issue comment self-reply detection: _fetch_issue_comments now collects
   bot replies and excludes human comments the bot already replied to.
   Bot replies use "> @user: text..." format — match that quote pattern
   against human comments to detect prior replies. This is the primary
   fix for the visible symptom (repeated identical replies).

2. In-progress dedup in _try_assignment_notification: the existing
   pending-mission check now also scans In Progress, preventing
   re-queuing while a review is still running.

Also adds review cooldown infrastructure to the notification tracker
(not yet wired — available for future use if needed).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When re-reviewing a PR after new commits are pushed, the review runner
was PATCHing the existing summary comment in-place.  GitHub does not
send notifications for edited comments, so the reviewer never saw the
updated review — it looked like the comment was never posted.

Now detects new commits vs prior review SHAs and posts a fresh comment
instead.  The old review is collapsed to a short "superseded" notice so
the PR timeline stays clean.  Retries of the same commits (no new code)
still skip as before.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Koan-Bot

Koan-Bot commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

Retargeted to upstream: Anantys-oss#1736

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.

3 participants