Session summary

default

scored

afi / afi · cli reviewed proxy at 9b51f95e with Default profile / Anthropic · opus 5 high on afi 0.30.0

Measurements

Unavailable values retain their exported state and reason

Quality

Recall 50%
Model-judged precision N/A
F1 N/A
Tier 1 recall 100%
Tier 2 recall 28.6%
Tier 3 recall 50%
Tier 4 recall 50%
Security recall 50%
Defect recall 61.5%
Maintainability recall 0%
Performance recall 33.3%
Median anchor distance 0
Worst anchor distance 0
Refusals a judge overturned Not recorded

Review outcomes

Distinct matched defects 10
Defects missed 10
Findings 10
Unkeyed findings 0
Intended findings 0
Carried findings Not recorded

Cost, usage, and speed

Total bill $0.7954
Review bill $0.7954
Judge bill N/A
Tokens 107659
Duration 2m 23s

Execution and identity

Lifecycle, reviewer, build, settings, and normalized execution limits

Lifecycle
completed
Lifecycle reason
Not recorded
Started
Finished
Reviewer
afi
Reviewer tool
afi · cli
Configuration
default / a5df80571ddb
Configuration ID
config-a1911a53d2684bd1
Build
afi 0.30.0 / b0f313c0b59a66ecc7612396dc8db0ea5da13a7a
Comparison
comparison-ca02d21e3600cf86
Build ID
build-0d3d7c7014d6b912
Cohort ID
cohort-07363c4dd57b37b6
Harness
bench 1 / 3c151df56e62cfdea90b84dd8b265f7e0f4bebff dirty
Adapter
afi 1 / sha256:a1a935298956020ee6d767a756847abb4c00694c88c2eb80d454886ebc4acf8c
Exit code
0
Wall time
143
Deadline exceeded
No
Budget
$12.00
Billing limit
$12.00

Usage

Normalized reviewer-tool usage projection

Input tokens
105232
Output tokens
2427
Cached input
0
Reasoning tokens
8342
Requests
3
Models counted
Not recorded

Judge

Normalized assessment summary

Judge data is N/A for this run

Findings

Review text is escaped; normalized decisions follow each finding

Finding f1

bug

forget(&index, &gone)` runs unconditionally, and `gone` is filled at line 137 even when `dry_run` is true — so `POST /v1/admin/cache/sweep?dry_run=true` deletes every index entry naming a blob the… forget(&index, &gone)` runs unconditionally, and `gone` is filled at line 137 even when `dry_run` is true — so `POST /v1/admin/cache/sweep?dry_run=true` deletes every index entry naming a blob the sweep *would* have removed (`fs::remove_file(path)` at line 252 has no dry-run guard). The route documented as "reports what one would reclaim without removing anything" (docs/operations.md, admin.rs:87) silently unlinks the name index, turning the next build's hits into a full refetch storm against upstreams.

bug
Category: bug Confidence: Not recorded

services/proxy/src/sweep.rs:140

Evidence quote

Not recorded

Suggested fix

Not recorded

matchedDefect 12 (locality)
Finding f2

bug

clear_partials` unlinks *every* file in `incoming` with no age or liveness test, but that directory holds the in-flight temporary of each running fetch (`BlobStore::temp_path`, store.rs:204) and of… clear_partials` unlinks *every* file in `incoming` with no age or liveness test, but that directory holds the in-flight temporary of each running fetch (`BlobStore::temp_path`, store.rs:204) and of each index write (store.rs:147). A sweep — automatic every `CAIRN_SWEEP_INTERVAL`, 15m by default — deletes the temp of a download that is proceeding fine; the writer's `fs::rename(&temp, &target)` at store.rs:259 then fails with ENOENT and the client gets a 500 on a perfectly good artifact. The doc comment's premise ("a fetch that is not coming back") is untrue for anything currently streaming.

bug
Category: bug Confidence: Not recorded

services/proxy/src/sweep.rs:232

Evidence quote

Not recorded

Suggested fix

Not recorded

matchedDefect 17 (locality)
Finding f3

bug

sort_by_key(|candidate| candidate.age)` sorts *ascending*, i.e. newest first, the opposite of the comment above it and of the eviction policy documented in config.rs:63. Combined with the `age >= min_age` gate at line 120, the sweep removes the youngest blobs that have just cleared the grace period and keeps the oldest, so the cache evicts exactly what a pipeline is still using. Needs `sort_by_key(|c| std::cmp::Reverse(c.age))`.

bug
Category: bug Confidence: Not recorded

services/proxy/src/sweep.rs:102

Evidence quote

Not recorded

Suggested fix

Not recorded

matchedDefect 1 (locality)
Finding f4

bug

values()` returns `sweep_bytes_reclaimed` in slot 4 and `sweep_blobs_removed` in slot 5, but `COUNTERS` (lines 50-57) names slot 4 `cairn_proxy_sweep_blobs_removed_total` and slot 5… values()` returns `sweep_bytes_reclaimed` in slot 4 and `sweep_blobs_removed` in slot 5, but `COUNTERS` (lines 50-57) names slot 4 `cairn_proxy_sweep_blobs_removed_total` and slot 5 `..._bytes_reclaimed_total`. The two sweep counters are exported under each other's names, so the metric docs/operations.md tells operators to alert on reports a blob count, not bytes. Swap the two loads.

bug
Category: bug Confidence: Not recorded

services/proxy/src/metrics.rs:94

Evidence quote

Not recorded

Suggested fix

Not recorded

matchedDefect 13 (locality)
Finding f5

performance

reclaim` calls the synchronous `Sweeper::sweep` directly on the runtime thread, as does `Sweeper::run` at sweep.rs:84 inside the spawned task. The module doc (sweep.rs:14-18) deliberately uses blocking `std::fs`, and routes/mod.rs:125 admits a sweep "legitimately takes longer than ten seconds" — that is a tokio worker blocked for the whole walk, stalling every download futures scheduled on it. Both call sites need `tokio::task::spawn_blocking`.

performance
Category: performance Confidence: Not recorded

services/proxy/src/routes/admin.rs:76

Evidence quote

Not recorded

Suggested fix

Not recorded

matchedDefect 11 (locality)
Finding f6

bug

the HTTP route bypasses the `running` mutex by calling `sweep()` instead of `run()`, so the "one sweep at a time" invariant the lock exists to keep (sweep.rs:10-12, 65-67) does not hold for the… the HTTP route bypasses the `running` mutex by calling `sweep()` instead of `run()`, so the "one sweep at a time" invariant the lock exists to keep (sweep.rs:10-12, 65-67) does not hold for the on-demand route. Two concurrent admin sweeps, or one racing the interval sweep, each compute `remaining` from a total the other is already shrinking and together take the store far below the ceiling.

bug
Category: bug Confidence: Not recorded

services/proxy/src/routes/admin.rs:76

Evidence quote

Not recorded

Suggested fix

Not recorded

matchedDefect 6 (locality)
Finding f7

bug

an unreferenced blob is removed with no grace period at all, but a miss commits the blob (cache.rs:151) before it writes the index entry (cache.rs:163) — during that window nothing points at it. A sweep landing there deletes the blob the request just stored; if it lands between the commit and `open_blob` (cache.rs:90) the request fails with the "already missing from the store" 500. The unreferenced branch should still respect `min_age`.

bug
Category: bug Confidence: Not recorded

services/proxy/src/sweep.rs:119

Evidence quote

Not recorded

Suggested fix

Not recorded

matchedDefect 18 (locality)
Finding f8

bug

fs::metadata` follows symlinks, contradicting the comment directly above it (a symlink is counted at its target's size, not the link's) and, worse, making `collect` recurse into symlinked… fs::metadata` follows symlinks, contradicting the comment directly above it (a symlink is counted at its target's size, not the link's) and, worse, making `collect` recurse into symlinked directories — a link pointing at an ancestor inside `CAIRN_BLOB_DIR` recurses until the stack overflows. Use `fs::symlink_metadata`.

bug
Category: bug Confidence: Not recorded

services/proxy/src/sweep.rs:164

Evidence quote

Not recorded

Suggested fix

Not recorded

matchedDefect 4 (locality)
Finding f9

bug

a failed `fs::remove_file` at line 126 only logs, then falls through to count the blob in `removed`/`bytes` and push it into `gone`. Space that was never reclaimed is reported to the operator and added to `cairn_proxy_sweep_bytes_reclaimed_total`, and its index entries are dropped though the blob is still there — `clear_partials` gets this right with its `continue` at line 234.

bug
Category: bug Confidence: Not recorded

services/proxy/src/sweep.rs:134

Evidence quote

Not recorded

Suggested fix

Not recorded

matchedDefect 2 (locality)
Finding f10

bug

remaining >= self.max_bytes` evicts when the store is exactly at the ceiling, though `cache_max_bytes` is documented as "the most the blob store may hold" (config.rs:63); should be `>`.

bug
Category: bug Confidence: Not recorded

services/proxy/src/sweep.rs:120

Evidence quote

Not recorded

Suggested fix

Not recorded

matchedDefect 3 (locality)

Source artifacts

Open a row to inspect or download evidence from this atomically published local run

findings present

Open verified source artifact

Open this path at the exported commit

The artifact is present; its content is available through the verified source link

meta present

Open verified source artifact

Open this path at the exported commit

The artifact is present; its content is available through the verified source link

record present

Open verified source artifact

Open this path at the exported commit

The artifact is present; its content is available through the verified source link

result present

Open verified source artifact

Open this path at the exported commit

The artifact is present; its content is available through the verified source link

spend present

Open verified source artifact

Open this path at the exported commit

The artifact is present; its content is available through the verified source link

stderr present

Open verified source artifact

Open this path at the exported commit

The artifact is present; its content is available through the verified source link

summary present

Open verified source artifact

Open this path at the exported commit

The artifact is present; its content is available through the verified source link

traffic present

Open verified source artifact

Open this path at the exported commit

The artifact is present; its content is available through the verified source link

benchee benchee-dashboard-1 built from 10f4ec58 Static benchmark evidence ·