Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
4a2d988
Let an agent account for a finding GitHub cannot close
kristofferR Jul 26, 2026
b0d60a0
Dismiss only what has no thread, and mean it everywhere
kristofferR Jul 26, 2026
c401ac3
Refuse the dismissals that would cost quota or bury a thread
kristofferR Jul 26, 2026
8361991
Give crq its own place on disk for a repository
kristofferR Jul 26, 2026
9dfcf4f
Act on a transition instead of logging it
kristofferR Jul 26, 2026
b0506d3
Keep one checkout from destroying another, and private source private
kristofferR Jul 26, 2026
7a21d30
Merge the workspace fixes into the watcher
kristofferR Jul 26, 2026
c1f63aa
Make a repeated dismissal succeed, and a partial one refuse
kristofferR Jul 26, 2026
9bff9ec
Authenticate the way the API client already does
kristofferR Jul 26, 2026
c5c2e86
Supersede the stale round instead of refusing the dismissal
kristofferR Jul 26, 2026
d054450
Stop the dispatcher losing work, quota, and its own arguments
kristofferR Jul 26, 2026
16c6004
Stop the mirror deleting the branches sessions create
kristofferR Jul 26, 2026
a3da74e
Judge a cooling round by whether it will fire, not whether it can now
kristofferR Jul 26, 2026
3fd635e
Dismiss the same repository it validated
kristofferR Jul 26, 2026
1708923
Migrate a mirror's refspec instead of only setting it at clone time
kristofferR Jul 26, 2026
663b906
Merge the mirror refspec migration
kristofferR Jul 26, 2026
f692045
Merge the mirror refspec migration
kristofferR Jul 26, 2026
c839f27
Say it out loud when no fix session can start
kristofferR Jul 26, 2026
b38778b
Run fix sessions beside each other, not inside the loop
kristofferR Jul 26, 2026
bfb4714
Give a fix session a log, and stop killing it when it succeeds
kristofferR Jul 27, 2026
59c7104
Make the unattended drain one command
kristofferR Jul 27, 2026
50a4196
Create the directory the service logs into
kristofferR Jul 27, 2026
476041f
Make a session write its log while it works
kristofferR Jul 27, 2026
a020c93
Rotate the pass so the tail is not starved of dispatch slots
kristofferR Jul 27, 2026
1358cef
Stop queueing the work that spends no quota
kristofferR Jul 27, 2026
07d0f92
Stop the drain from losing what it was supposed to protect
kristofferR Jul 27, 2026
f6adcd0
Let a fix session push the work it just did
kristofferR Jul 27, 2026
52a9bf2
Learn the account block from any notice, not just the round that asked
kristofferR Jul 27, 2026
e51bb97
Stop the calibration probe shortening a block it cannot see
kristofferR Jul 27, 2026
67408ca
Leave refs/heads to the sessions, and stop swallowing a failed fetch
kristofferR Jul 27, 2026
c117445
Refuse the partial dismissal that would queue the new head
kristofferR Jul 27, 2026
ec3270b
Pin the replayed dismissal the guard used to refuse
kristofferR Jul 27, 2026
ad2d6a0
Give the drain a head it can dispatch and a service that can run
kristofferR Jul 27, 2026
036c830
Answer credentials for GitHub only, and stop pruning live checkouts
kristofferR Jul 27, 2026
384e3e0
Let the fixes land instead of holding them for a queued round
kristofferR Jul 27, 2026
589fd10
Pin the other side of "a review worth holding for"
kristofferR Jul 27, 2026
2ca6e83
Finish the migration of a mirror an older crq made
kristofferR Jul 27, 2026
892525c
Keep a session's branch and collect a checkout stamped ahead
kristofferR Jul 27, 2026
cefba8b
Stop the drain wasting the work it is in the middle of
kristofferR Jul 27, 2026
689bcab
Let the drain run an agent other than claude
kristofferR Jul 27, 2026
9d9d456
Make a reinstall replace the running drain
kristofferR Jul 27, 2026
85ceaa4
Merge remote-tracking branch 'origin/main' into feat/watch-dispatch
kristofferR Jul 27, 2026
ceb6deb
Read the policy from the configuration, not the Service
kristofferR Jul 27, 2026
b417a21
Merge remote-tracking branch 'origin/feat/repo-local-workspace' into …
kristofferR Jul 27, 2026
141e284
Harden and isolate repository workspaces
kristofferR Jul 27, 2026
8fd9962
Fix unattended review drain edge cases
kristofferR Jul 27, 2026
b9e4f59
Pin one fix session per pull request, at three levels
kristofferR Jul 27, 2026
e31cf52
Make a dispatch claim say what it saw
kristofferR Jul 27, 2026
7f0caf7
Leave the mirror usable by the caller that works in it
kristofferR Jul 27, 2026
8cf4d78
Fix unattended drain review failures
kristofferR Jul 27, 2026
06de125
Preserve detached session branches during mirror migration
kristofferR Jul 27, 2026
082d35d
Name every other crq that writes the same state
kristofferR Jul 27, 2026
89e76f5
Retry interrupted legacy head cleanup
kristofferR Jul 27, 2026
4ad4591
Do not run a fix session on somebody else's branch
kristofferR Jul 27, 2026
aff8190
Decide the heartbeat's verdict inside the attempt that reached it
kristofferR Jul 27, 2026
bb5a7ea
Address review feedback on dismissal flow
kristofferR Jul 27, 2026
3b8287f
Read the skip marker the same way the watcher's oracle does
kristofferR Jul 27, 2026
7958262
Protect active workspace checkouts
kristofferR Jul 27, 2026
ebc1041
Fix unattended dispatch edge cases
kristofferR Jul 27, 2026
27880bd
Fix unattended dispatch review handling
kristofferR Jul 27, 2026
e0bf7f4
Harden shared mirror refreshes
kristofferR Jul 27, 2026
0f89da8
Address review drain edge cases
kristofferR Jul 27, 2026
f508166
Isolate shared workspace Git state
kristofferR Jul 27, 2026
ba313b2
Fix detached review dispatch handling
kristofferR Jul 27, 2026
fd760d3
Harden shared workspace isolation
kristofferR Jul 27, 2026
cfa4f90
Harden unattended review dispatch
kristofferR Jul 27, 2026
0fff5e8
Fix drain review edge cases
kristofferR Jul 27, 2026
0352236
Answer the five findings the drain's own review raised
kristofferR Jul 27, 2026
81cb088
Take the hold branch's Markdown scanner for the skip marker
kristofferR Jul 27, 2026
d21ee55
Make fixing what watching does, and off a per-repository answer
kristofferR Jul 27, 2026
47f7ae4
Give the push gate the escape hatch it depends on
kristofferR Jul 27, 2026
ee7b63a
Stop the push gate creating states a session cannot leave
kristofferR Jul 27, 2026
44960f7
Push before resolving, because only one order is recoverable
kristofferR Jul 27, 2026
1531704
Fix review dispatch race conditions
kristofferR Jul 27, 2026
bd67d23
Harden unattended review dispatch
kristofferR Jul 27, 2026
38c8e46
Fix unattended review drain safeguards
kristofferR Jul 27, 2026
567a698
Fix unattended dispatch review findings
kristofferR Jul 27, 2026
134e1f0
Make CRQ_EXCLUDE mean it everywhere
kristofferR Jul 27, 2026
144a66c
Carry the denylist into the installed unit too
kristofferR Jul 27, 2026
91c009f
Take the base branch's workspace, and follow it where it moved
kristofferR Jul 27, 2026
5eacb77
Merge remote-tracking branch 'origin/main' into feat/watch-dispatch
kristofferR Jul 27, 2026
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Dependency rule (Go-enforced, no cycles): `dialect ← engine ← crq`, `state
Owns persistent filesystem and process I/O for checkouts; `crq` supplies only
configured roots and a current-token resolver.
- `internal/state/` — persisted schema v4: one `Round` per PR, one global
- `internal/state/` — persisted schema v3: one `Round` per PR, one global
`FireSlot`, the CodeRabbit `AccountQuota`, an `Archive` ring. Round transition
methods, durable tombstones for tidied trigger comments, the CAS store, and
dashboard rendering. `Round.CoBots` holds per-
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ crq feedback <repo> <pr> # current normalized findings as JSON, WITHOUT trigger
crq threads <repo> <pr> # every unresolved thread, outdated included
crq resolve <thread-id> [<thread-id>...] # resolve addressed review threads
crq decline <thread-id> [...] --reason "<why>" [--resolve] # record why a finding is declined
crq drain install # ⭐ unattended: watch every PR and fix what needs fixing
crq watch # what the drain runs: drive open PRs through crq next, one JSON
# line each (--dispatch starts a fix session; --once for cron)
crq hold <repo> <pr> --reason "<why>" # persistently stop reviews for a PR
crq unhold <repo> <pr> # resume reviews for a held PR
crq hold # list held PRs
Expand Down Expand Up @@ -486,7 +489,7 @@ Set these in `~/.config/crq/env` (sourced automatically) or as environment varia
| `CRQ_SCOPE` | owner of `CRQ_REPO` | which owners/orgs share this quota (comma-separated) |
| `CRQ_STATE_REF` | `crq-state-v3` | git ref that stores the typed CAS state |
| `CRQ_REPOS` | _(all in scope)_ | `autoreview` allowlist — only these `owner/name` repos (comma-separated) |
| `CRQ_EXCLUDE` | _(none)_ | `autoreview` denylist — never these `owner/name` repos (comma-separated) |
| `CRQ_EXCLUDE` | _(none)_ | denylist — crq never reviews, watches or fixes these `owner/name` repos (comma-separated) |
| `CRQ_AUTOREVIEW_SKIP_AUTHORS` | `dependabot[bot]` | PR authors `autoreview` never enqueues (comma-separated; case and `[bot]` suffix don't matter) — set to empty to auto-review bot PRs too; manual `crq review` is unaffected |
| `CRQ_AUTOREVIEW_SKIP_MARKER` | `<!-- crq:skip-autoreview -->` | exact PR-body marker that suppresses fleet auto-review; set empty to disable; manual `crq loop` is unaffected |
| `CRQ_TIDY` | `0` | set to `1` to delete crq's own spent review-trigger comments as rounds progress (`crq tidy` by hand is unaffected) |
Expand Down
62 changes: 62 additions & 0 deletions cmd/crq/doctor_installs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"os"
"path/filepath"
"testing"
)

// One stale crq erased every dispatch claim in the fleet, and nothing said so.
// doctor cannot stop it — the binary that does the damage is older than any
// mechanism meant to catch it — but it can name it.
func TestOtherInstallsNamesADifferentBuild(t *testing.T) {
self, err := os.Executable()
if err != nil {
t.Fatal(err)
}
mine, err := os.ReadFile(self)
if err != nil {
t.Fatal(err)
}

stale := t.TempDir()
same := t.TempDir()
if err := os.WriteFile(filepath.Join(stale, "crq"), []byte("an older build"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(same, "crq"), mine, 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", stale+string(os.PathListSeparator)+same)
t.Setenv("GOPATH", t.TempDir()) // no crq there: an empty directory is not a finding

found := otherInstalls()
if len(found) != 2 {
t.Fatalf("found %d installs, want the two planted ones: %+v", len(found), found)
}
byPath := map[string]otherInstall{}
for _, in := range found {
byPath[in.Path] = in
}
if in, ok := byPath[filepath.Join(stale, "crq")]; !ok || in.SameBuild {
t.Errorf("the stale build was not reported as different: %+v", in)
}
if in, ok := byPath[filepath.Join(same, "crq")]; !ok || !in.SameBuild {
t.Errorf("a copy of this build was reported as different: %+v", in)
}
}

// A directory on PATH that holds no crq, or holds something unexecutable, is
// not a second install.
func TestOtherInstallsIgnoresWhatItCannotRun(t *testing.T) {
empty := t.TempDir()
unexec := t.TempDir()
if err := os.WriteFile(filepath.Join(unexec, "crq"), []byte("notes"), 0o644); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", empty+string(os.PathListSeparator)+unexec)
t.Setenv("GOPATH", t.TempDir())
if found := otherInstalls(); len(found) != 0 {
t.Errorf("reported %+v, want nothing", found)
}
}
Loading