Skip to content

One pull request, start to finish

Example #1215, from the earlier reviews that taught Argus to the push that fixed it, then the comments Argus posts when it doesn't review. Everything here was written for this page in Argus's formats, around real files in Argus's repository. Argus did not post it.

The review

The full review of #1215. Three of its lines have notes that link to the sections below.

Example

#1215

feat(config): make the recovery claim window configurable

argus-eye bot reviewed

Argus · 8/10 — Stale window of 1800 becomes 1.8µs, not 30 minutes

Fix 1 blocking finding before you merge. Argus reviewed 4 files.

What this PR does · 1 author check
  • Make the recovery claim window configurable with RECOVERY_STALE_AFTER

Author's checks

  • An invalid value stops startup with an error

1 finding suppressed by team feedback (audit)

Page note: Dropped before posting: it matched a finding @maintainer dismissed on #1164, at similarity 0.91. The drop floor is 0.80. See #1164

Minor notes (1)
  • No test checks that an invalid RECOVERY_STALE_AFTER stops startup · backend/internal/config/config.go:L148
usage: 118.4k tokens · 4 stages

Contract: production/full · checked: single-pass review · 1 suppressed by team feedback · review took 1m38s

Per-stage usage for the example review on pull request #1215
StageModelTokensCost
Intentgoogle/gemini-2.5-flashgoogle/gemini-2.5-flash3.2k$0.0016
Triagegoogle/gemini-2.5-flashgoogle/gemini-2.5-flash4.1k$0.0021
Reviewanthropic/claude-sonnet-4.5anthropic/claude-sonnet-4.5101.3k$0.3579
Scoringgoogle/gemini-2.5-flashgoogle/gemini-2.5-flash9.8k$0.0045

Dashboard (on a real install this links to your dashboard) · React thumbs-down on an inline comment to dismiss it · Reply to an inline comment or use @argus-eye help

Learned: 1 pattern · 1 PR summary · 1 finding memory · 1 file memory

backend/internal/config/config.go +8 −0

3 other hunks in this file are not shown: the "time" import, the Config field and the struct literal.

Diff of backend/internal/config/config.go, old lines 141 to 146, new lines 143 to 153
Old lineNew lineChangeCode
@@ -141,6 +143,11 @@ func Load() (*Config, error) {
141143 if err != nil {
142144 return nil, fmt.Errorf("invalid MAX_CONCURRENT_REVIEWS: %w", err)
143145 }
146added staleSecs, err := strconv.Atoi(getEnv("RECOVERY_STALE_AFTER", "1800"))
147added if err != nil {
148added return nil, fmt.Errorf("invalid RECOVERY_STALE_AFTER: %w", err)
149added }
150added recoveryStaleAfter := time.Duration(staleSecs) Argus finding on this line
144151 embedDims, err := strconv.Atoi(getEnv("EMBEDDINGS_DIMENSIONS", "1024"))
145152 if err != nil {
146153 return nil, fmt.Errorf("invalid EMBEDDINGS_DIMENSIONS: %w", err)

argus-eye bot

Blocking · Bug: Stale window of 1800 becomes 1.8µs, not 30 minutes

time.Duration(staleSecs) counts nanoseconds, so the default RECOVERY_STALE_AFTER=1800 becomes 1.8µs. The recovery sweep treats any run older than this as crashed, so it can claim a review that is still running and run it a second time.

Suggested change

removed recoveryStaleAfter := time.Duration(staleSecs)
added recoveryStaleAfter := time.Duration(staleSecs) * time.Second

— Matches a prior fix in PR #1172.

Page note: Cited: a pattern Argus stored from a finding its judge scored 94 on #1172. "A prior fix" is the label; Argus does not check that a fix merged. See #1172

Argus did not compile or run this fix · React thumbs-down to dismiss

@contributor pushed a41f0d2

@contributor pushed 3e9c1a7

argus-eye bot

Resolved by 3e9c1a7 — the flagged lines were modified in this push.

Resolved argus-eye marked this conversation as resolved.

Page note: Resolved after a judge model read push 3e9c1a7 against the finding. The push before it changed the line without fixing it, and the thread stayed open. See both pushes

Fig. 1 — Example review, written for this page in Argus's format; Argus did not post it. The unchanged diff lines are real Argus source (backend/internal/config/config.go:141-146). Everything else is illustrative, and argus-eye stands in for your App's name.

Where two of its lines came from

Four example pull requests in order: what Argus posted on each, and what it stored.

  1. #1164 feat(config): add EMBEDDINGS_DIMENSIONS

    A maintainer dismisses a finding and says why.

    Example

    inline thread · backend/internal/config/config.go line 144

    backend/internal/config/config.go

    Diff of backend/internal/config/config.go, new line 144
    LineChangeCode
    144added embedDims, err := strconv.Atoi(getEnv("EMBEDDINGS_DIMENSIONS", "1024")) Argus finding on this line

    argus-eye bot

    Suggestion · Bug: An empty EMBEDDINGS_DIMENSIONS falls back to 1024 without logging

    getEnv returns the fallback for an empty value, so a deploy that sets EMBEDDINGS_DIMENSIONS= runs with 1024 dimensions and logs nothing.

    React thumbs-down to dismiss

    maintainer

    Intended. Every setting in config.go treats an empty variable as unset; getEnv does that on purpose.

    argus-eye bot model-written

    Understood: getEnv treats an empty value as unset for every setting in this file, so the fallback is expected. Resolving.

    Page note: Stored: a dismissal of this finding, with @maintainer's reply as the developer explanation. Write access is checked first.

    Page note: Stored: a pattern with the reply's lesson for this repo: "config.go settings treat an empty variable as unset; falling back to the default is intended."

  2. #1172 feat(app): make the heap-profile interval and cooldown configurable

    A high-scoring finding becomes memory, and so does a convention in the diff.

    Example

    inline thread · backend/internal/app/memprof.go line 75

    backend/internal/app/memprof.go

    Diff of backend/internal/app/memprof.go, new lines 73 to 75
    LineChangeCode
    73added Interval: time.Duration(envInt("MEMPROF_INTERVAL_SEC", defaultMemProfIntervalSec)) * time.Second,
    74 RSSThresholdMB: uint64(envInt("MEMPROF_RSS_THRESHOLD_MB", defaultMemProfRSSThresholdMB)),
    75added Cooldown: time.Duration(envInt("MEMPROF_COOLDOWN_SEC", defaultMemProfCooldownSec)), Argus finding on this line

    argus-eye bot

    Blocking · Bug: Snapshot cooldown of 300 becomes 300ns, not 5 minutes

    time.Duration counts nanoseconds, so the default MEMPROF_COOLDOWN_SEC=300 becomes 300ns. Every 30-second sample above the 400 MB threshold then writes a heap profile to disk, which the cooldown exists to prevent.

    Suggested change

    removed Cooldown: time.Duration(envInt("MEMPROF_COOLDOWN_SEC", defaultMemProfCooldownSec)),
    added Cooldown: time.Duration(envInt("MEMPROF_COOLDOWN_SEC", defaultMemProfCooldownSec)) * time.Second,

    Argus did not compile or run this fix · React thumbs-down to dismiss

    Page note: Stored: a pattern from the finding, which the judge scored 94. Single-pass reviews store findings scored 90 or higher; without a scoring model, or when the judge call fails, they store blocking and warning findings instead.

    Page note: Stored: a convention read from this PR's added lines, where the interval setting already multiplies by time.Second: "Convention [architecture]: duration settings are integer seconds, converted with * time.Second"

  3. #1190 feat(app): parse the memprof interval with time.ParseDuration

    Two conventions disagree, and a maintainer picks one.

    Example

    PR conversation comment

    argus-eye bot commented · edited

    Convention conflict

    This PR establishes [architecture]: duration settings are parsed with time.ParseDuration, for example 30s, which conflicts with [architecture]: duration settings are integer seconds, converted with * time.Second learned from PR #1172. Confirm which convention stands:

    Page note: Stored: this PR's convention, "Convention [architecture]: duration settings are parsed with time.ParseDuration, for example 30s".

    Page note: @maintainer, who has write access, ticked the second box on Aug 20, 2026.

    Page note: Then: the #1190 convention is marked superseded by the #1172 one, and reviews stop retrieving it.

  4. #1215 feat(config): make the recovery claim window configurable

    A month later, #1215's review drops one finding and cites another.

    Shown in Fig. 1

    Example

    dashboard review page, restyled for legibility and shown open

    Withheld findings (1)
    • backend/internal/config/config.go:L146 Closely matches a finding your team dismissed (similarity 0.91)

      Suggestion · Bug: An empty stale-window setting falls back to 1800 without a log line getEnv returns the fallback for an empty value, so a deploy that sets it to an empty string runs with 1800 and logs nothing. React thumbs-down to dismiss

    Page note: Read: 1 finding dropped as a close match to the #1164 dismissal.

    Page note: Read: 1 finding cited at similarity above 0.80 to the pattern stored from #1172.

Fig. 2 — Four example PRs in one repository, in Argus's real formats. The diff lines are Argus source as it reads today (backend/internal/config/config.go:144 and backend/internal/app/memprof.go:73-75), except #1172's unfixed line 75, which was written for this example. PRs, dates, SHAs, handles, scores and text are illustrative. Lines marked ↳ are this page's notes.

Two pushes, one fix

The first push changes the flagged line without fixing it, so the thread stays open and the next summary counts it. The second fixes it, and Argus resolves its own thread.

Push a41f0d2: the line changed, the bug didn't

Example
example/argus · #1215 · push a41f0d2 ·

@contributor pushed a41f0d2 fix(config): convert RECOVERY_STALE_AFTER to a duration

backend/internal/config/config.go

Line 150 of backend/internal/config/config.go, before and after push a41f0d2
LineChangeCode
150removed recoveryStaleAfter := time.Duration(staleSecs)
150added recoveryStaleAfter := time.Duration(staleSecs) * time.Millisecond

Blocking · Bug: Stale window of 1800 becomes 1.8µs, not 30 minutes

No reply from Argus. Unresolved

Summary for this push:

argus-eye bot reviewed

Argus (Incremental) · 9/10 — 1 unresolved earlier thread

Resolve 1 earlier blocking thread before you merge. Argus reviewed 1 file in one pass. The diff matches the stated goal.

… collapsed blocks not shown

1 earlier finding may still apply to files this PR changes
  • backend/internal/config/config.go · Stale window of 1800 becomes 1.8µs, not 30 minutes · from example/argus#1215

… usage block not shown


Dashboard (on a real install this links to your dashboard) · @argus-eye help

Learned: 1 PR summary

Page note: 1800 × time.Millisecond is 1.8 seconds, still wrong. The judge did not confirm a fix, so the thread stays open and the summary counts it. The earlier-finding row comes from a separate re-read of the stored #1215 finding (up to five per push, with Simulation and scenarios on).

Push 3e9c1a7: fixed

Example
example/argus · #1215 · push 3e9c1a7 ·

@contributor pushed 3e9c1a7 fix(config): read RECOVERY_STALE_AFTER in seconds

backend/internal/config/config.go

Line 150 of backend/internal/config/config.go, before and after push 3e9c1a7
LineChangeCode
150removed recoveryStaleAfter := time.Duration(staleSecs) * time.Millisecond
150added recoveryStaleAfter := time.Duration(staleSecs) * time.Second

Blocking · Bug: Stale window of 1800 becomes 1.8µs, not 30 minutes

argus-eye bot

Resolved by 3e9c1a7 — the flagged lines were modified in this push.

Resolved argus-eye marked this conversation as resolved.

Summary for this push:

argus-eye bot reviewed

Argus (Incremental) · 10/10 — No findings

Argus reviewed 1 file in one pass. The diff matches the stated goal.

… collapsed blocks not shown


Dashboard (on a real install this links to your dashboard) · @argus-eye help

Learned: 1 PR summary

Page note: 1800 × time.Second is 30 minutes. The judge confirmed the fix, so Argus resolved its thread. The reply's "lines were modified" wording is fixed text; the judge made the call.

Fig. 3 — The two pushes to #1215, in Argus's real formats.

The receipt

The usage block from Fig. 1, opened. Its first line says what the review ran; the table lists each stage's model and tokens.

Example
#1215 · usage block, opened

usage: 118.4k tokens · 4 stages

Contract: production/full · checked: single-pass review · 1 suppressed by team feedback · review took 1m38s

Per-stage usage for the example review on PR #1215
StageModelTokensCost
Intentgoogle/gemini-2.5-flashgoogle/gemini-2.5-flash3.2k$0.0016
Triagegoogle/gemini-2.5-flashgoogle/gemini-2.5-flash4.1k$0.0021
Reviewanthropic/claude-sonnet-4.5anthropic/claude-sonnet-4.5101.3k$0.3579
Scoringgoogle/gemini-2.5-flashgoogle/gemini-2.5-flash9.8k$0.0045
Fig. 4 — The usage block from Fig. 1, opened. Example values; the Cost column is left out when no cost is known.
Contract: production/full
The change class and the review depth.
checked: single-pass review
The reviewers that ran.
1 suppressed by team feedback
Findings memory dropped before posting.
review took 1m38s
Time from the start of the run until the summary was written.
The table
One row per stage, with its model. Later spend, such as re-checks on push, shows on the dashboard.

A review with no findings

The docs follow-up to #1215 changed two Markdown files. The review raised nothing, so the summary says what it read and stops.

Example

example/argus · Pull request #1221

docs: document RECOVERY_STALE_AFTER Open

@contributor wants to merge 1 commit into main from docs/recovery-stale-window

argus-eye bot reviewed

Argus · 10/10 — No findings

Argus reviewed 2 files in one pass. The diff matches the stated goal.

What this PR does
  • Document RECOVERY_STALE_AFTER in the self-hosting guide and README
usage: 35.5k tokens · 2 stages

Contract: docs/full · checked: single-pass review · review took 41s

Per-stage usage for the example review on pull request #1221
StageModelTokensCost
Intentgoogle/gemini-2.5-flashgoogle/gemini-2.5-flash1.9k$0.0010
Reviewanthropic/claude-sonnet-4.5anthropic/claude-sonnet-4.533.6k$0.1188

Dashboard (on a real install this links to your dashboard) · @argus-eye help

Learned: 1 PR summary

Fig. 5 — #1221, a docs follow-up to #1215. No scoring ran, because the review returned no findings.

When Argus doesn't review

With auto-review off, each pull request gets a checkbox with a cost preview instead of a review. A pull request over the size limits gets a refusal that says why.

Example
example/argus · #1234 · fix(api): return 404 for unknown review ids

argus-eye bot commented

Argus review

Auto-review is off for this repo. Tick the box below to run a review on this PR.

Estimated cost

  • Files changed: 2
  • Diff lines (±): 18
  • Historical avg: ~96.2k tokens · ~$0.29 · across last 20 review(s)

Tip: you can also comment @argus-eye review at any time.

Example
example/argus · #1230 · chore(web): regenerate API client fixtures

argus-eye bot commented

Argus did not review this pull request.

this pull request changes 412 files, over the limit of 400 — split it, or raise the limit in settings

Fig. 6 — Two comments Argus posts instead of a review. First, #1234, after a maintainer switched auto-review off for example/argus on Sep 15, 2026; the estimate is the repo's average over its last 20 reviews, not a prediction for this PR. Second, #1230, which changed 412 files.

At 60 files or 1,500 changed lines, Argus reviews the 40 highest-risk files at reduced depth and says so. At 400 files or 20,000 lines, it refuses. Settings → Limits changes both.

Your coding agent reads the same memory

Over MCP, an agent can ask Argus for the memory briefing its reviewer gets for a file.

Example

claude · ~/src/argus

I'm adding a duration setting to config.go. Check Argus memory first.Skill(argus-memory)argus - list_repos (MCP){"repos":[{"repo_id":3,"full_name":"<owner>/argus","installation_id":1,"enabled":true,"default_branch":"main"}]}argus - get_memory_briefing (MCP)(repo_id: 3, query: "duration settings in config.go", file_path: "backend/internal/config/config.go"){"markdown": "…", "empty": false}## File History
- config.go reads each setting with getEnv and strconv. #1215 passed a seconds value to time.Duration without * time.Second, so an 1800-second window became 1.8µs.

## Established Patterns
1. Convention [architecture]: duration settings are integer seconds, converted with * time.Second
2. Confirmed pattern [bug]: Snapshot cooldown of 300 becomes 300ns, not 5 minutes. `time.Duration` counts nanoseconds… (file: backend/internal/app/memprof.go)
3. config.go settings treat an empty variable as unset; falling back to the default is intended.

## Past Review Findings (avoid re-raising the same issue)
1. Stale window of 1800 becomes 1.8µs, not 30 minutes. `time.Duration(staleSecs)` counts nanoseconds, so the default `RECOVERY_STALE_AFTER=1800` becomes 1.8µs…

## Known False Positives (DO NOT re-flag these patterns)
1. An empty `EMBEDDINGS_DIMENSIONS` falls back to 1024 without logging

Developer explanation: Intended. Every setting in config.go treats an empty variable as unset; getEnv does that on purpose.

Apply these patterns and past findings when reviewing. When a finding matches a known pattern above, add a tag at the end of your comment: *[Matches pattern: <pattern description>]*. Only tag when there is a clear match — do not fabricate references.[model-written] This repo reads duration settings as integer seconds and multiplies by time.Second, and an empty variable falling back to its default is intended. I'll parse the new value with strconv.Atoi and multiply by time.Second.
Fig. 7 — Example Claude Code session on Sep 10, 2026, after the four PRs above. The briefing is illustrative and clipped, the superseded #1190 convention is not in it, and the reviewer's own query can return different entries. The File History line and the agent's reply are model-written.

MCP setup /docs/mcp

Run Argus on your own repositories

Open source under AGPL-3.0, with no paid tier and no feature gating.

Self-hosted only: Docker Compose or Fly.io, Postgres with pgvector, a GitHub App and a Clerk app you create, your model keys and an embeddings endpoint.