Skip to content

roborev can report a clean verdict without having reviewed the requested content (4 issues confirmed on v0.61.2) #1011

Description

@pmcfadin

Context

We drive roborev from automated agents as a merge gate — a review must be confirmed clean before a PR
is allowed to land. That use case makes us unusually sensitive to one failure mode: roborev returning a
plausible-looking clean verdict when it did not actually review the requested content.
A vacuous
No issues found. is textually identical to a genuine clean pass, so an automated consumer cannot tell them
apart without out-of-band evidence.

This is not hypothetical for us. On one of our changes, two runs reported "No issues found"; re-run correctly
against the right commit, the same diff produced two genuine blocking findings. Accepting the first
verdict would have merged unreviewed code.

We ended up building a defensive wrapper that verifies roborev's output against independently-computed facts.
In doing so we found the following. Everything in §1 I reproduced directly on v0.61.2; §2 is honestly
flagged as older evidence I have not re-verified; §3 is a doc/UX note, not a bug.

Happy to split this into separate issues if you'd prefer — each section is independently actionable. Also
happy to test patches; we exercise this heavily.

Environment: roborev v0.61.2 (static release), Linux x86_64, --agent codex --model gpt-5.6-sol,
daemon healthy, 4 workers. Clean database (roborev repo list was empty at session start), so the job ids
below are a fresh 1 and 2.


1. Confirmed on v0.61.2

1a. --wait returns before the job record is durable

roborev review <sha> --wait returns, but the job record is not yet readable. Immediately after the
command exited, roborev list --json for job 2 had no git_ref, no status, no model, and no
token_usage
. Queried again moments later, all four were populated:

id=2 status='done' git_ref='155e12c8e8d1af9f75e47ca854e87f489b818fe6' model='gpt-5.6-sol' token_usage=<present>

Repro: run roborev review <sha> --repo <abs> --agent codex --model gpt-5.6-sol --wait, then immediately
roborev list --json (or roborev show <job> --json) in the same shell. Re-query a few seconds later.

Impact: a consumer that reads the record right after --wait — the natural thing to do — silently gets a
degraded view. In our case four independent verification signals fell back to weaker sources simultaneously,
with no error and no indication anything was missing. We now poll with a bounded retry, but ideally --wait
would not return until the record it produced is readable (or would expose an explicit "record durable"
signal).

1b. token_usage is a JSON-encoded string nested inside JSON

"token_usage": "{\"input_tokens\":67387,\"cached_input_tokens\":43520,\"total_output_tokens\":2232,\"usage_source\":\"job_log_turn_completed\",...}"

It's a string field whose contents are JSON, so every consumer must decode twice:
json.loads(json.loads(record)["token_usage"]).

Impact — this one cost us a real, silent defect. Our first implementation parsed one level and read the
field names we'd inferred from the binary's struct tags. Result: our token-based check reported
UNAVAILABLE on every single run — a verification step that appeared to exist but never once executed.
We only caught it by noticing the field was never populated on a real review. Emitting token_usage as a
nested object would remove this whole class of consumer error.

1c. Asymmetric token field naming

Input is input_tokens and cached_input_tokens, but output is total_output_tokens. Minor on its own,
but it's the specific inconsistency that made 1b's silent failure easy to write and hard to spot — we
guessed output_tokens and got a plausible-looking "no data" result rather than an error.

1d. roborev repo has no add subcommand, so a git worktree cannot be registered

Available subcommands are list, show, rename, move, delete, merge. Registration only happens
implicitly on first use.

We use git worktree extensively (one worktree per in-flight change), and an unregistered worktree appears
to be the root cause of the --branch misresolution in §2a below: with no registered repo matching the cwd,
--branch has no correct repo to resolve against. A roborev repo add <path> would make the worktree case
expressible, and would also let a consumer verify registration rather than hoping first-use did the right
thing.


2. Older evidence, NOT re-verified on v0.61.2

Flagging the verification gap rather than presenting these as current facts — they may already be fixed. I
can re-test and follow up if useful.

2a. --branch from inside a git worktree resolves against the ROOT checkout and reviews the BASE commit

Observed enqueueing 39900e4db (= origin/main, the base) when the worktree branch HEAD was 4e7ab591e.
Five consecutive jobs all enqueued the base commit. The reviewer then receives an empty diff and replies
No issues found. Summary: The provided combined diff contains no code changes to review.

Likely narrower framing: this may be entirely about the absence of an explicit --repo. If --branch
resolves correctly when given --repo <abs-worktree>, then the precise bug is "--branch silently resolves
against a different repo when the cwd is an unregistered worktree" — which would be both clearer and more
tractable. Either way, silently resolving to a different commit than the user asked for seems worth an
error rather than a review.

2b. The two-positional commit-range form enqueued a commit that is neither endpoint

roborev review 89fdbb895 989d7d2c3 enqueued 90a17d376 — neither the start nor the end of the range.

2c. A code-free (docs-only) diff is silently discarded on a correctly-targeted run

roborev review <sha> --repo <abs> --agent codex --model gpt-5.6-sol --wait on a 5-file, +167/−63,
all-markdown diff: correct SHA enqueued, correct repo, and yet
No issues found. Summary: The provided diff contains no code changes to review. Reproducible across two
runs.

Token accounting was the only way to tell: 18,700 input / 0 cached / 53 output / 8s, versus 398k–649k
input with 314k–554k cached and 5.0k–6.3k output over ~2.5 minutes for genuine reviews of comparable diffs.
(A known-empty diff measured 17,333 input / 21 output — i.e. the docs-only run sat right on the empty-diff
baseline.)

This is the most consequential one, because it passes an enqueued-SHA check — right SHA, right repo,
plausible clean verdict — so verifying the reviewed commit cannot catch it. If a code-free diff is
deliberately out of scope, an explicit non-zero refusal would be enormously better than a verdict that is
byte-identical to a clean pass.


3. Doc/UX note (not a bug)

3a. roborev review <sha> reviews one commit, which is easy to misread as "the branch"

Confirmed on v0.61.2: commit 155e12c touches 3 files, while the branch's diff against its base is 27
files / +3603/−74
. Only the single commit's diff reached the reviewer (confirmed via the prompt length and
content).

This is presumably intended semantics. But an automated caller told to "review this branch" that reaches for
the single-SHA form gets a partial review reported as a complete one — for any multi-commit branch,
"clean" silently means "the last commit was clean." A line in review --help distinguishing the single-commit
and branch-range forms, and/or echoing the reviewed file count in the output, would make this
self-evident. Echoing the file count would incidentally make 2c self-diagnosing too.


The one change that would matter most

The unifying theme across 1a, 2a, 2b and 2c is that each fails by producing a plausible success rather than
an error
:

When roborev does not actually review the requested content — wrong repo, wrong or partial range,
unsupported/code-free diff, or a record that isn't readable yet — it would be far safer to fail loudly and
non-zero
than to return a clean verdict.

For interactive use a vacuous "No issues found." is a minor annoyance. For anything gating a merge, it's
indistinguishable from success, and that's the difference between a review tool and a review guarantee.

Thanks for roborev — it's genuinely useful, and the findings it produces when correctly targeted have caught
real bugs for us. These reports are all in service of trusting it more, not less.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions