Skip to content

crq full code review (post-shipping quality pass)#6

Merged
kristofferR merged 43 commits into
review-basefrom
review
Jun 23, 2026
Merged

crq full code review (post-shipping quality pass)#6
kristofferR merged 43 commits into
review-basefrom
review

Conversation

@kristofferR

@kristofferR kristofferR commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Fresh CodeRabbit + Codex review of the current shipped crq code (main @ 076810b).

The tool is already shipped & deployed; this PR re-reviews the consolidated code end-to-end to catch anything the incremental rounds missed. Findings will be fixed on main.

Known/intentional (please don't re-litigate): the deferred large-scope items are tracked in #2#5, and the README <details> collapsibles are intentional (markdownlint MD033 declined). Everything else: fix away.

Summary by CodeRabbit

  • New Features
    • Added crq autoreview daemon with --once and --no-incremental, repo allow/deny filtering, and improved per-host coordination
    • Added crq version and crq help
  • Bug Fixes
    • Hardened distributed lock and staleness handling; improved completion/in-flight detection with head-SHA deduping and stronger error propagation
    • Improved queue/pump/wait/cancel correctness and reduced default lock backoff
  • Documentation
    • Major README refresh: updated setup/loop guidance, configuration, troubleshooting, and safer review triggering
  • Examples
    • Added examples/monitor.sh and examples/review-loop.sh; removed examples/agent-loop.sh

…docs

Hardening found during live testing:
- _lock_ref_sha: gh dumps 404 bodies to stdout and skips --jq on errors, so a
  free lock looked 'held' and acquire never created it — validate the SHA now.
- lock age via an epoch embedded in the lock commit message (one fetch, our format)
  instead of a fragile committer.date lookup; exponential backoff with jitter so a
  thundering herd can't trip GitHub secondary rate limits and starve everyone.
- rate-limit signal: calibration (@coderabbitai rate limit) is authoritative; a stale
  WARNING never overrides a fresh 'available now'. WARNING scan is fallback only.
- pump: lockless idle fast-path; single post-gc in-flight check; gc closed-PR scan
  gated by CRQ_GC_TTL and only drops on explicit 'closed' (not on API error bodies).
- CRQ_DRY_RUN for safe testing.

Docs: friendlier README (clear problem statement, how it works, recommended PR
review loop, troubleshooting, LLM cheat-sheet); examples/review-loop.sh + monitor.sh;
Claude skill.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
crq only controls review timing if CodeRabbit auto-review is OFF; otherwise
CodeRabbit reviews every push automatically, bypassing the queue and burning the
shared rate limit. Add a required-prerequisite callout to README + SKILL.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…review

With native CodeRabbit auto-review off (so crq controls the rate), 'crq autoreview'
reproduces it, coordinated: enqueues every open PR in scope whose HEAD CodeRabbit
hasn't reviewed (new PR = auto-review; new commits = incremental) and fires them FIFO
when unblocked. Flags mirror CodeRabbit's toggles:
  default          = Automatic + Incremental review
  --no-incremental = Automatic review only (first review, no re-review on push)
  --once           = single pass (cron/monitor)
The gate repo (calibration PR) is excluded. README/SKILL updated; the caveat now
explains you don't lose auto-review — crq re-creates it rate-aware.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
crq autoreview is an account-wide feature for all PRs, not a PR-loop add-on — give it
its own top-level section ahead of the agent-loop section, framed around reviewing every
open PR. Drop the migration/'getting it back' framing.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…omplement

Restore the previous section wording; reword so it's clear every open PR gets
auto-reviewed (the queue only orders to respect the rate limit, it's not a cap).

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…al install + launchd

- Dedup: record the commit each review was requested for ('.fired' map). pump drops a
  queued PR without re-posting if a review was already requested for that exact HEAD, and
  autoreview skips it too — so autoreview + an agent's 'crq wait' never double-review a commit.
- 'crq version' / --version / -v; help and version work without gh installed.
- Preflight: clear errors if gh or jq is missing, or if gh isn't logged in (gh auth token).
- autoreview never touches the gate repo (calibration PR).
- README: collapsed manual-install section; collapsed launchd/systemd persistent-run setup;
  note that autoreview + crq wait never double-post.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…reviewed history

- Bump to 1.0.0.
- Dashboard issue redesigned: status line, info table, queue and a new 'Recently
  reviewed' history (last 20), every PR rendered as a GitHub link, timestamps humanized.
- State gains a capped 'history' array; pump appends each fired review.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
Previously, after calibration read 'available now', crq trusted it for CRQ_CALIBRATE_TTL
(~120s) even if a fired review then got rate-limited — so it could keep firing into
WARNINGs. Now: when an in-flight review's PR shows a fresh rate-limit WARNING, set
blocked_until from that WARNING's countdown immediately and back off; a fresh
'available now' calibration no longer overrides a fresh WARNING-derived block.

Dashboard: show 'available now' / '✅ not currently limited' when there's capacity (was
an empty '?' / '—'), and '⛔ resets <time> (~Nm)' with remaining 0 when rate-limited.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…r autoreview

By default autoreview covers all open PRs in CRQ_SCOPE; set CRQ_REPOS to restrict to
specific owner/name repos, or CRQ_EXCLUDE to skip some. Comma-separated, spaces tolerated.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
Dashboard times render via strflocaltime under CRQ_TZ (IANA, e.g. Europe/Oslo);
defaults to UTC. Internal state stays ISO8601 UTC.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
… machine state)

The human dashboard is now rendered to the gate repo's README.md so the repo's home
page IS the live status. To avoid commit spam it's only committed when user-visible
content changes (queue / in-flight / rate-limit / history) — gated by a content
signature, not every internal tick. The tracking issue keeps the machine-readable
state JSON; its title still shows one-glance status.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…ial README dashboard

crq init now opens https://github.com/<gate> (its README is the live dashboard) in the
user's default browser (open/xdg-open/wslview/powershell; CRQ_NO_OPEN=1 to skip), and
publishes the initial README dashboard on first creation. Browser-open and the
initial-state publish are guarded so re-running init never wipes live state.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
- review-loop.sh: wait_for_review returns 1 on timeout (was returning 0 because the
  final sleep succeeded), so callers can tell 'review landed' from 'timed out'.
- monitor.sh: fail fast if REPO is empty (not in a git repo / no default) instead of
  building malformed repos//pulls/... API URLs.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
- acquire: nonce fallback (urandom/PID) when uuidgen is absent + never proceed on empty
  nonce; release refuses an empty nonce (P1).
- acquire: steal pre-epoch/foreign locks via the commit's committer.date when epoch= is
  absent (avoids deadlock-until-manual-unlock) (P2).
- pump: require a zero exit AND numeric comment id before marking fired/in_flight, else
  leave the PR queued (gh prints error bodies to stdout) (P1).
- gc: a rate-limited fire is requeued and its fired-marker cleared, so dedup/autoreview
  don't skip that commit forever (P1).
- state_write: propagate issue-write failure; mutating callers no longer report success
  on a failed persist (P2).
- autoreview: --paginate the reviews lookup so the latest review wins (P2); --limit 1000
  on PR search so >30 open PRs are covered (P2); run enqueue/pump in subshells so a
  transient die can't kill the watcher (P2).

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
- cmd_wait: enqueue inside the loop and check its status; don't treat 'absent from queue'
  as 'fired' when the enqueue write failed. Subshells isolate a transient die (P2).
- gc timeout path: requeue the timed-out PR and clear its fired-marker (same fix as the
  rate-limited path) so the commit isn't skipped forever (P2).
- monitor.sh + global loop monitor: --paginate the latest-review lookup so HEAD-vs-reviewed
  uses the newest review, not page 1 (P2).

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
- inflight_status / calib reply / warning scan: '[...]|last|{...}' emitted {u:null,b:null}
  on an EMPTY match, so an in-flight review with no warning was misread as warning:0 and
  immediately requeued (risking duplicate reviews), and empty calib/warning matches parsed
  as garbage. Use 'last // empty | {...}' so an empty match emits nothing (P1).
- autoreview + warning scan: --archived=false on gh search prs, so an archived (read-only)
  repo's open PR can't be enqueued and then jam the FIFO head (P1).
- state_write: publish the README dashboard BEFORE storing readme_sig; on publish failure
  keep the old signature so the next change retries (no permanently-stale dashboard) (P2).
- (crq:444 timeout fired-marker was already fixed in df337c6.)

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
- cmd_wait: check in-flight/queue completion BEFORE (re)enqueuing, so another runner
  firing our PR doesn't recreate a duplicate queue entry; only treat 'absent' as fired
  once we've actually enqueued (enqueued flag) (P2).
- cmd_cancel: clear the .fired marker when cancelling, so a stuck review can be retried
  for the same commit instead of being dedup-dropped (P2).
- autoreview: case-insensitive gate-repo exclusion (GitHub owner/repo names fold case),
  so a differently-cased CRQ_REPO can't let the calibration PR get auto-reviewed (P2).

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
- inflight_status: check the PR /reviews endpoint (and put the timeout backstop LAST), so a
  review that completed via a formal PR review isn't wrongly timed-out + requeued -> no
  duplicate review for an already-reviewed commit (P2).
- autoreview: case-insensitive CRQ_REPOS / CRQ_EXCLUDE / gate-repo matching (P2).
- state_write: include .blocked.source and .warn in the README signature so dashboard-only
  changes still republish (P3).
- examples/monitor.sh: bot_count also counts issues/<pr>/comments (CodeRabbit conversation
  feedback), else unattended monitor can miss it (P2).
- examples/review-loop.sh: source ~/.config/crq/env before the CRQ_REPO guard; skip the
  fix/push round when wait_for_review times out (P2).

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
crq init now sets the calibration PR's notification subscription to IGNORED ('None') so
its repeated '@coderabbitai rate limit' comments don't ping the user. Needs the gh
'notifications' scope; logs a clear hint (gh auth refresh -h github.com -s notifications)
and falls back to a manual step when the scope is absent. Re-asserted on every init.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…RIBED for authored PRs)

The calibration PR is authored by the user, so updateSubscription(IGNORED) resolves to
UNSUBSCRIBED ('None'); accept that as success instead of falsely reporting a failure.
The durable hard-ignore can only be set via the UI bell-slash (documented in README).

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
gh api --paginate runs a --jq filter once PER PAGE, so '[...]|length' / '[...]|last' return
multi-line values (e.g. '0\n1') once a PR exceeds one page (~100 items) — numeric tests and
object parses then silently misfire. Fixed every affected lookup to '--slurp | jq "add | ..."'
(gh forbids --slurp with --jq, so pipe to a standalone jq):
- inflight_status: wl (warning), revd (reviews), newer (comments)
- _calib_latest_reply, _warning_latest
- examples/review-loop.sh + README loop snippet + global monitor bot_count

Also:
- inflight_status: require fully-parsed u/b before taking the WARNING path, so a transient gh
  error body can't trip the rate-limit requeue (crq:416).
- README loop snippet: carry a success flag and 'continue' on wait timeout, so a copied loop
  doesn't push a round on stale/missing feedback (crq README:292).

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
- cmd_wait: run pump (its gc clears a COMPLETED prior-round in-flight) BEFORE the in-flight
  check, so a stale same-PR in_flight isn't mistaken for this waiter's freshly-fired review —
  the documented loop's next round now actually fires a review for the new commit (crq:619).
- Normalize repo casing (lowercase) at cmd_enqueue/cmd_wait/cmd_cancel entry and in autoreview's
  state pre-check + fired key, so a casing mismatch (owner/repo vs Owner/Repo) can't miss queued/
  in-flight state and post a duplicate review (crq:808).
- autoreview: per-pass scan cap (CRQ_AUTOREVIEW_MAX_SCAN, default 400) on the PRs that incur
  head+reviews REST lookups, so a huge no-allowlist scope can't blow GitHub's ~900-pt/min
  secondary limit; defers the rest to the next pass with a log (crq:818).
- examples/monitor.sh: compare the REMOTE PR head, not the local checkout (monitor:40).
- examples/review-loop.sh: wait_for_review also counts formal PR reviews (review-loop:37);
  check crq wait's status before polling/processing (review-loop:46).

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…on-PR email spam)

The calibration PR gets repeated '@coderabbitai rate limit' comments, and crq posts them AS
the user — which re-subscribes the author, so a per-PR unsubscribe never holds and CodeRabbit's
replies keep emailing them (making the integration unusable). The gate repo is machine-only and
never read by a human, so crq init now sets Watch -> Ignore on the WHOLE repo via
'PUT /repos/{repo}/subscription {ignored:true}' (only needs the 'repo' scope). Replaces the
unreliable per-PR _mute_pr with _mute_repo.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…flood at the source)

pump's lockless fast-path only skipped work when the cached quota reading was FRESH, so an idle
daemon (empty queue, nothing in flight) with a STALE reading would acquire the lock and post
'@coderabbitai rate limit' every tick — flooding the calibration PR (and, before the repo was
ignored, the user's inbox). A quota reading is only needed when there's actually a review
waiting to fire, so now: idle (qn==0 && in_flight==null) returns immediately without calibrating;
calibration happens on-demand when work is queued. Bounds calibration to the active-work window.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…review storm)

Round 7 lowercased the fired-marker LOOKUP key ($rlc) but existing markers were stored with
the original repo casing, so autoreview stopped matching them and re-fired already-reviewed PRs
(one per pass, rate-limited) — a real re-review/email storm. gc now folds all fired keys (and
history repo names) to lowercase, merging duplicate-cased entries, so old markers migrate and
dedup catches them. Self-healing and idempotent.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
- acquire: age a held lock by GitHub's commit timestamp (server time, skew-free) instead of the
  client epoch embedded in the message, so a host with a bad clock can't delete a live lock (crq:135).
- autoreview: ROTATE the per-pass scan budget (cursor advances each pass, wraps at the end) instead
  of break-ing at the same point, so scopes larger than the cap don't starve their tail PRs (crq:831).
- cmd_wait: pump immediately after a fresh enqueue (and re-check) instead of sleeping a full poll,
  so on-demand waits fire as soon as capacity is free (crq:643).
- examples/review-loop.sh + README loop: exclude rate-limit WARNING comments from 'feedback'
  detection, count formal PR reviews too, and 'crq wait || continue' so a copied loop doesn't
  push a round on stale/absent feedback.

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…ount-wide leader lease)

The multi-daemon mess (overlapping launchd respawns + manual runs all scanning + pumping) was the
root of the lock contention and re-fire churn. Now:
- per host: a pidfile guard so a second 'crq autoreview' on the same machine exits immediately.
- account-wide: a leader ref (CRQ_LEADER_REF) in the gate repo, claimed via atomic
  create-if-not-exists, heartbeated each pass, and stealable only after CRQ_LEADER_TTL of staleness.
  Across all hosts exactly one daemon is the boss; the rest stand by and fail over if it dies.
  Leadership is dropped on EXIT/INT/TERM so failover is immediate on a clean stop.
Agent crq wait/enqueue/pump stay parallel — only the autoreview daemon is singleton.

Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
- cmd_autoreview: validate CRQ_SCOPE (empty scope made the owner loop silently no-op) (CodeRabbit crq:893)
- pump dry-run: propagate state_write failure like the live paths (CodeRabbit crq:664)
- examples/monitor.sh: bot_count excludes rate-limit WARNING comments, so the monitor doesn't
  false-wake as NEW_FEEDBACK when no actual review arrived (CodeRabbit monitor.sh:30)

Declined/already-fixed: monitor.sh remote-PR-head and review-loop.sh timeout/review-count were
already fixed in rounds 7-8 (CodeRabbit re-flagged stale snippets); README markdownlint (MD031 +
inline-HTML) declined — the <details> collapsibles and blockquoted fences are intentional and
render correctly on GitHub. Codex round 9 surfaced nothing new (converged).

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
Codex findings on the singleton/round-8 code:
- gc also lowercases .queue/.in_flight repo names (not just .fired/.history), so old mixed-case
  entries migrate and aren't re-enqueued/un-cancelable (crq:568)
- inflight_status requires a parseable FUTURE reset before returning warning: — otherwise gc
  requeued with no blocked_until and pump could repost in a tight loop (crq:487)
- _leader_beat propagates write failure; _leader_try returns it, so a leader that can't refresh
  its heartbeat re-contends instead of staying 'leader' while a standby steals the stale ref (crq:218)
- cmd_wait checks CRQ_WAIT_TIMEOUT at the top of the loop, so a persistently-failing enqueue can't
  hang the caller past the timeout (crq:709)
- autoreview: SIGINT/SIGTERM now exit (trap was cleaning up but bash resumed the loop -> untracked
  daemon, hard to stop) (crq:876)
- autoreview: log when a per-owner search hits gh's 1000-result cap instead of silently truncating (crq:934)

Per user: crq is a CodeRabbit coordinator — keep it CodeRabbit-only. examples/monitor.sh now
watches 'coderabbitai' (was 'coderabbitai|chatgpt-codex'); the Codex+CodeRabbit dual-review is
only our private loop for reviewing crq, not part of the shipped tool. (crq core had zero Codex refs.)

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
…nsitively (CodeRabbit)

Round-9 excluded rate-limit WARNING posts only from the issue-comment counter and matched the
marker case-sensitively. Apply the exclusion uniformly across all three bot_count counters
(comments/reviews/issue-comments) via a shared case-insensitive filter, so warning-only bot
activity never spuriously emits NEW_FEEDBACK regardless of which endpoint or marker casing.

Ref #1
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
@kristofferR

Copy link
Copy Markdown
Owner Author

@codex review

@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f6cd52de40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crq
[ "$idx" -le "$scan_cursor" ] && continue
if [ "$scanned" -ge "$CRQ_AUTOREVIEW_MAX_SCAN" ]; then deferred=1; continue; fi
scanned=$((scanned+1))
head="$(gh api "repos/$repo/pulls/$pr" --jq '.head.sha // empty' 2>/dev/null | cut -c1-9)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh leadership during long autoreview scans

When multiple hosts run crq autoreview, the leader heartbeat is only refreshed at the start of each pass, but this per-PR scan can take longer than the default CRQ_LEADER_TTL=180 when scanning hundreds of PRs (CRQ_AUTOREVIEW_MAX_SCAN=400) and doing head/review lookups plus enqueue writes. In that case a standby can decide the ref is stale and steal leadership while the original leader is still scanning, so the advertised singleton turns into two daemons doing the same expensive scan/enqueue work and increasing secondary-limit pressure; heartbeat inside the long scan or make the TTL cover the worst-case pass duration.

Useful? React with 👍 / 👎.

Comment thread README.md Outdated
Comment thread examples/monitor.sh
Comment thread crq

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
examples/review-loop.sh (1)

43-45: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use inclusive timestamp checks to avoid boundary misses.

since is second-granularity; strict > can miss feedback that lands in the same second and force a false timeout path. Switch these filters to >=.

Suggested patch
-        | jq --arg bot "$BOT" --arg rl "$RL" --arg since "$since" 'add | map(select(.user.login==$bot and ((.updated_at // .created_at) > $since) and ((.body//"")|ascii_downcase|contains($rl|ascii_downcase)|not))) | length' 2>/dev/null)
+        | jq --arg bot "$BOT" --arg rl "$RL" --arg since "$since" 'add | map(select(.user.login==$bot and ((.updated_at // .created_at) >= $since) and ((.body//"")|ascii_downcase|contains($rl|ascii_downcase)|not))) | length' 2>/dev/null)
@@
-        | jq --arg bot "$BOT" --arg since "$since" 'add | map(select(.user.login==$bot and .submitted_at > $since)) | length' 2>/dev/null)
+        | jq --arg bot "$BOT" --arg since "$since" 'add | map(select(.user.login==$bot and .submitted_at >= $since)) | length' 2>/dev/null)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/review-loop.sh` around lines 43 - 45, The timestamp comparisons in
both jq filter expressions use strict greater-than `>` operators with the
`since` variable, which can miss feedback that lands in the same second due to
second-level granularity of the timestamp. Change the comparison operators from
`>` to `>=` in both places: update the filter that checks `(.updated_at //
.created_at) > $since` to use `>=` instead, and update the filter that checks
`.submitted_at > $since` to use `>=` instead. This ensures inclusive boundary
checks that won't miss feedback entries created in the same second as the since
timestamp.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@examples/review-loop.sh`:
- Around line 43-45: The timestamp comparisons in both jq filter expressions use
strict greater-than `>` operators with the `since` variable, which can miss
feedback that lands in the same second due to second-level granularity of the
timestamp. Change the comparison operators from `>` to `>=` in both places:
update the filter that checks `(.updated_at // .created_at) > $since` to use
`>=` instead, and update the filter that checks `.submitted_at > $since` to use
`>=` instead. This ensures inclusive boundary checks that won't miss feedback
entries created in the same second as the since timestamp.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e3d76004-c63e-4dce-bdc6-fcb62d9f4096

📥 Commits

Reviewing files that changed from the base of the PR and between 9e1b0fd and f6cd52d.

📒 Files selected for processing (2)
  • crq
  • examples/review-loop.sh
📜 Review details
🔇 Additional comments (5)
examples/review-loop.sh (1)

57-61: LGTM!

crq (4)

229-238: LGTM!


547-548: LGTM!

Also applies to: 559-560


977-979: LGTM!


1059-1059: 📐 Maintainability & Code Quality

Version claim for gh api --slurp is accurate.

The --slurp flag was indeed added in GitHub CLI 2.48.0, so the upgrade guidance in the error message is correct.

- pump: before firing, also dedup against the reviews endpoint — if the bot already reviewed THIS
  commit (e.g. a slow review landed after a timeout-requeue cleared the fired marker), record it and
  drop without posting a duplicate (crq:560)
- examples/monitor.sh: hex-validate the remote head lookup (error-body gotcha) before enqueueing (monitor:59)
- README sample loop: handle crq wait's deduped(3)/timeout exit codes (process existing feedback on
  3, back off on error) instead of treating any non-zero as skip (README:283)

Declined (tracked in #4): crq:226 / crq:942 / crq:976 — heartbeat/lease during long scans and
force-overwrite; the fundamental no-CAS leader items, corruption-safe via the state lock.

Ref #6
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
@kristofferR

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: b77d3a055e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
README.md (1)

346-347: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document crq wait exit code 3 in the command reference.

Line 346-Line 347 omits the dedup/already-reviewed return code (3), which is part of cmd_wait behavior. This can mislead loop implementations that rely on this section.

Suggested patch
-`<repo>` is `owner/name`; `<pr>` is the number. **Exit codes:** `crq wait` returns `0` once your
-review is fired (or `2` if `CRQ_WAIT_TIMEOUT` is hit); other commands return `0` on success.
+`<repo>` is `owner/name`; `<pr>` is the number. **Exit codes:** `crq wait` returns `0` when a new
+review is fired, `3` when the current head was already reviewed (deduped), and `2` if
+`CRQ_WAIT_TIMEOUT` is hit; other commands return `0` on success.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 346 - 347, The documentation for `crq wait` command
exit codes in the README.md is incomplete and missing the exit code 3 behavior.
Update the exit codes section (around lines 346-347) to include documentation
for exit code 3, which represents the dedup/already-reviewed case. Add this exit
code to the existing exit code documentation list that currently only mentions
exit codes 0 and 2 to ensure users implementing loops have complete information
about all possible return values from the `crq wait` command.
crq (1)

678-687: 🎯 Functional Correctness | 🟠 Major

Validate full SHA before truncating to short form.

The current code truncates the GitHub API response to 9 characters before validation, allowing any non-empty all-hex string (regardless of origin) to pass as a valid commit SHA. Validate the complete response is exactly 40 lowercase hex characters, then derive the 9-character prefix. This ensures we catch truncated or corrupted API responses that happen to produce valid-looking hex strings.

🛡️ Proposed fix
-  local nonce json n bu bu_epoch lf lf_epoch qn cfresh infp head repo pr seq cid hsha key lastsha crrev_p
+  local nonce json n bu bu_epoch lf lf_epoch qn cfresh infp head repo pr seq cid hsha hsha_full key lastsha crrev_p
...
-    hsha="$(gh api "repos/$repo/pulls/$pr" --jq '.head.sha // empty' 2>/dev/null | cut -c1-9)"
+    hsha_full="$(gh api "repos/$repo/pulls/$pr" --jq '.head.sha // empty' 2>/dev/null)"
     key="$repo#$pr"
     lastsha="$(jq -r --arg k "$key" '.fired[$k] // empty' <<<"$json")"
@@
-    if [ -z "$hsha" ] || [ -n "${hsha//[0-9a-f]/}" ]; then
+    if [ "${`#hsha_full`}" -ne 40 ] || [ -n "${hsha_full//[0-9a-f]/}" ]; then
       # Couldn't read a valid head SHA (transient/secondary-limit) — DON'T fire with a bogus SHA;
       # leave the PR queued so the next pump retries once the API recovers.
       log "skip: could not read a valid $key head SHA (transient) — leaving it queued"
+    else
+      hsha="${hsha_full:0:9}"
-  local repo pr head crrev crrev_raw reviewed reviewed_raw json present key firedsha rlc scanned idx deferred scan_cursor=0 searched
+  local repo pr head head_full crrev crrev_raw reviewed reviewed_raw json present key firedsha rlc scanned idx deferred scan_cursor=0 searched
...
-        head="$(gh api "repos/$repo/pulls/$pr" --jq '.head.sha // empty' 2>/dev/null | cut -c1-9)"
+        head_full="$(gh api "repos/$repo/pulls/$pr" --jq '.head.sha // empty' 2>/dev/null)"
         # Require a real short SHA — a failed lookup leaves a non-empty NON-hex error body (gh dumps
         # it to stdout, --jq skipped), which must not be treated as a head and drive an enqueue (crq:975).
-        [ -n "$head" ] && [ -z "${head//[0-9a-f]/}" ] || continue
+        [ "${`#head_full`}" -eq 40 ] && [ -z "${head_full//[0-9a-f]/}" ] || continue
+        head="${head_full:0:9}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crq` around lines 678 - 687, The hsha variable assignment currently truncates
the GitHub API response to 9 characters using cut before any validation occurs,
which allows corrupted or truncated responses that happen to be valid hex to
pass validation. Refactor the assignment to first capture the full response from
the gh api call without truncating, then validate that the complete response is
exactly 40 characters and contains only lowercase hex digits, and only after
successful validation should the 9-character prefix be extracted and assigned to
hsha. Update the validation logic if needed to check the full 40-character
response before the truncation happens.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crq`:
- Around line 692-699: The current elif statement combines the gh api call and
jq parsing in a way that fails silently when either command errors, leaving
crrev_p empty and causing the condition to evaluate false, which allows
execution to fall through to the duplicate post branch. Separate the gh api
command from the jq parsing by first executing the gh api call and explicitly
checking its exit status, then pipe only successful output to jq and check that
result separately. Add explicit error handling or exit logic to prevent falling
through to the live post branch when either the gh api call or jq parsing fails
transiently, ensuring that duplicate reviews are not posted due to temporary API
or parsing errors.

---

Outside diff comments:
In `@crq`:
- Around line 678-687: The hsha variable assignment currently truncates the
GitHub API response to 9 characters using cut before any validation occurs,
which allows corrupted or truncated responses that happen to be valid hex to
pass validation. Refactor the assignment to first capture the full response from
the gh api call without truncating, then validate that the complete response is
exactly 40 characters and contains only lowercase hex digits, and only after
successful validation should the 9-character prefix be extracted and assigned to
hsha. Update the validation logic if needed to check the full 40-character
response before the truncation happens.

In `@README.md`:
- Around line 346-347: The documentation for `crq wait` command exit codes in
the README.md is incomplete and missing the exit code 3 behavior. Update the
exit codes section (around lines 346-347) to include documentation for exit code
3, which represents the dedup/already-reviewed case. Add this exit code to the
existing exit code documentation list that currently only mentions exit codes 0
and 2 to ensure users implementing loops have complete information about all
possible return values from the `crq wait` command.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d6338af8-d6f7-4f4e-b0aa-c7fe2df1c7d0

📥 Commits

Reviewing files that changed from the base of the PR and between 9e1b0fd and b77d3a0.

📒 Files selected for processing (4)
  • README.md
  • crq
  • examples/monitor.sh
  • examples/review-loop.sh
📜 Review details
🔇 Additional comments (4)
examples/monitor.sh (1)

60-62: LGTM!

examples/review-loop.sh (1)

43-45: LGTM!

Also applies to: 57-61

README.md (1)

178-345: LGTM!

Also applies to: 349-420

crq (1)

228-247: LGTM!

Also applies to: 547-548, 559-560, 623-677, 688-691, 700-726, 922-983, 988-1028, 1058-1084

Comment thread crq Outdated
…-dedup (crq:699)

The crq:560 dedup elif combined the reviews lookup + jq + the >0 test in one condition, so a failed
gh/jq returned empty and the elif evaluated false -> fell through to the live post, reopening the
duplicate window on transient errors. Split it: skip (leave queued) if the reviews lookup or its jq
parse fails; only dedup when a review at this commit is actually confirmed.

Declined (tracked in #4): crq:226/950/984 — the fundamental no-CAS leader heartbeat/lease items.

Ref #6
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
@kristofferR

Copy link
Copy Markdown
Owner Author

@codex review

…e tool)

The crq:378 preflight 'gh api --help | grep --slurp' is environment-flaky (pager/output variance),
and a false negative made it die 'gh too old' on a gh that DOES support --slurp — taking down crq
enqueue and crash-looping the autoreview daemon. Downgrade it to a non-fatal warning: never hard-fail
on this heuristic. (Regression from the round-4 crq:378 fix.)

Ref #6
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…pping

Replace the env-dependent 'gh api --help | grep --slurp' (which false-negatived, then false-warned)
with a parse of 'gh --version': fail only when the version is DEFINITIVELY < 2.48.0, and proceed
silently if it can't be parsed. No false warnings, no risk of bricking on a heuristic.

Ref #6
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
@kristofferR

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2a27efa3d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crq
cur="$(_leader_ref_sha)"; [ -n "$cur" ] || return 1
case "$(_lock_commit_msg "$cur")" in *"$_LEADER_ID epoch="*) ;; *) return 1 ;; esac
sha="$(_leader_commit)"; [ -n "$sha" ] || return 1
gh api -X PATCH "repos/$CRQ_REPO/git/refs/heads/$CRQ_LEADER_REF" -f sha="$sha" -F force=true >/dev/null 2>&1 # 0/1 propagated

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid force-updating a stolen leader ref

If this leader is paused after the ownership check above and another daemon steals the stale ref after CRQ_LEADER_TTL, the resumed process still patches the ref with force=true, which overwrites the new leader unconditionally; GitHub's refs API describes force=false as the mode that avoids overwriting work (https://docs.github.com/rest/git/refs#update-a-reference). In that pause/steal window, both autoreview daemons can believe they are leader and run scans concurrently, so the heartbeat should be a conditional fast-forward/CAS-style update rather than a forced ref write.

Useful? React with 👍 / 👎.

Comment thread crq
while true; do
# Re-assert leadership each pass (also our heartbeat). If a stale period let another host
# steal it, step down and stand by until we can reclaim — never two leaders scanning at once.
if ! _leader_try; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh leadership during long scans

This heartbeat is only performed before the autoreview pass; with the default cap of 400 scanned PRs, the head/reviews/comment lookups plus enqueue calls can easily take longer than CRQ_LEADER_TTL=180s in a large scope. When that happens, a standby treats this still-running daemon as stale and starts its own scan, so the singleton guarantee is lost until the original loop reaches the next iteration; heartbeat periodically inside the scan loop or size the TTL above the worst-case pass duration.

Useful? React with 👍 / 👎.

Comment thread crq
scan_cursor=0
fi
( cmd_pump ) >/dev/null 2>&1 || true # subshell: a transient pump die can't kill the watcher
[ "$once" = "1" ] && break

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist the autoreview cursor for --once

When crq autoreview --once is used from cron or a timer and there are more than CRQ_AUTOREVIEW_MAX_SCAN scan-eligible PRs, this break exits right after deferring the tail and the in-memory scan_cursor is lost. The next cron invocation starts at offset 0 again, so the same first 400 PRs are scanned every time and PRs beyond the cap may never be enqueued; either persist the cursor across --once runs or keep scanning until the deferred tail is covered before exiting.

Useful? React with 👍 / 👎.

Comment thread README.md Outdated
@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Round-6 added exit 3 (already-reviewed/deduped) but the README/help only listed 0/2. Document it
so callers handle 'already reviewed -> process existing feedback' (README:347).

Declined (tracked): crq:1030 = --once cursor persistence (crq:909; in-process rotation only, minor
--once-only edge), and crq:226/950/954/988 = the fundamental no-CAS leader items (issue #4).

Ref #6
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c05444d555

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread examples/monitor.sh
echo "monitor PR#$PR repo=$REPO base=$BASE"
while true; do
CUR=$(bot_count) || { sleep 60; continue; } # transient API failure -> skip this tick, don't false-wake
[ "$CUR" != "$BASE" ] && { echo "NEW_FEEDBACK $BASE -> $CUR"; exit 0; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track edited bot feedback in the monitor

When CodeRabbit updates an existing walkthrough/summary comment for a new review instead of adding a new comment, the issue/review counts returned by bot_count stay identical, so this comparison never exits with NEW_FEEDBACK and an unattended loop can sit until IDLE_TIMEOUT despite fresh feedback. Track the newest updated_at/submitted_at timestamp (while still excluding rate-limit warnings) rather than only comparing counts.

Useful? React with 👍 / 👎.

Comment thread README.md
Comment on lines +300 to +301
new=$(gh api "repos/$REPO/issues/$PR/comments" --paginate --slurp \
| jq "add | map(select(.user.login==\"coderabbitai[bot]\" and .created_at > \"$since\" and (.body|contains(\"rate limited by coderabbit.ai\")|not))) | length")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Poll edited CodeRabbit comments in the README loop

Users copying this recommended loop can still miss a completed review when CodeRabbit refreshes an existing summary/walkthrough comment: the poll only counts issue comments whose created_at is newer than since, so an edited bot comment with a newer updated_at is ignored and the loop times out without processing the feedback. Match the standalone example by comparing updated_at // created_at for issue comments.

Useful? React with 👍 / 👎.

Comment thread crq
[ -n "$reset" ] || continue
if [ "$ue" -gt "$best_u" ]; then best_u="$ue"; best_reset="$reset"; found=1; fi
done < <(gh search prs --owner "$owner" --state open --json number,repository \
done < <(gh search prs --owner "$owner" --state open --archived=false --limit "$CRQ_WARNING_SCAN_LIMIT" --json number,repository \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Sort warning probes by recency before applying the cap

In an owner with more than CRQ_WARNING_SCAN_LIMIT open PRs and calibration unavailable, this only inspects the first default search results before looking for warning comments. The gh manual documents --limit as the maximum results fetched and the default --sort as best-match (with updated available), so a fresh CodeRabbit rate-limit warning outside those first 40 PRs is invisible; crq can then clear blocked_until and post another review while the account is still limited.

Useful? React with 👍 / 👎.

Comment thread crq
Comment on lines +1008 to +1009
if ! reviewed_raw="$(gh api "repos/$repo/issues/$pr/comments" --paginate --slurp 2>/dev/null)"; then continue; fi
reviewed="$(printf '%s' "$reviewed_raw" | jq --arg bot "$CRQ_BOT" --arg m "$CRQ_REVIEW_DONE_MARKER" 'add | map(select(.user.login==$bot and ((.body//"")|contains($m)))) | length' 2>/dev/null)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check PR summaries outside issue comments

In --no-incremental mode, a PR with no formal CodeRabbit review relies on this issue-comment scan to decide whether a review already happened, but CodeRabbit's default PR summary is written into the PR description unless users move it to the walkthrough (see https://docs.coderabbit.ai/pr-reviews/summaries). For summary-only completions using that default placement, the marker is never present in issues/$pr/comments, so autoreview --no-incremental can enqueue and spend another review on a PR that was already reviewed once.

Useful? React with 👍 / 👎.

@kristofferR
kristofferR merged commit 460d18d into review-base Jun 23, 2026
1 check passed
kristofferR added a commit that referenced this pull request Jun 23, 2026
I called convergence before Codex re-reviewed the last commit; it found 4 valid items:
- _warning_latest: --sort updated so the bounded scan probes the most-recently-active PRs, not an
  arbitrary best-match slice, before the cap (crq:472)
- autoreview --no-incremental: also check the PR DESCRIPTION for CodeRabbit's summary (its default
  location), not just issue comments, before deciding a PR is unreviewed (crq:1009)
- examples/monitor.sh: bot_count emits 'count@latest-timestamp' so an EDITED summary/walkthrough
  (count unchanged) still changes the signature and wakes the loop (monitor:55)
- README sample loop: poll updated_at (// created_at) so an edited bot comment isn't missed (README:301)

Ref #6
Claude-Session: https://claude.ai/code/session_01QVnKpFmFMYvRTyeoYozXst
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.

1 participant