feat: store review-pass model input and show it on the comparison page #79
No reviewers
Labels
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
brooktrails/pr-reviewer!79
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/review-prompt-storage"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Persists the exact input each config's review call sent to the model — resolved system prompt +
BuildUserMessagerendering — and surfaces it on/admin/comparisons/{id}as a collapsed Model input section per config.prompt_system/prompt_userTEXT columns onreview_events(default''; old rows and rows whose review pass never ran read back empty, and the template hides the section)ResolveReviewSystemPrompthelper, now used by all three backends'Reviewmethods, so the stored copy cannot drift from what was sentNote: full-tier prompts embed the diff (≤100k chars) and changed-file contents (≤50k/file), so rows grow into the hundreds-of-KB range per config. They TOAST out of line; growth is visible on the new
/admin/storagepage (#78).🤖 Generated with Claude Code
Automated review by pr-reviewer v0.37.0 | Safety Check | Mistral Small | tracking id
r-61e9ac-f8f489This is an AI-generated review and may contain mistakes.
Status: ✅ Completed
✅ Verdict: LGTM — No major issues found.
No obvious bugs, security issues, or accidentally committed files detected.
4927e4be2b08b7c39c93Automated review by pr-reviewer v0.37.0 | Safety Check | Mistral Small | tracking id
r-61fefe-bc2a40This is an AI-generated review and may contain mistakes.
Status: ✅ Completed
✅ Verdict: LGTM — No blocking issues found. Changes appear safe.
No obvious bugs, security issues, or accidentally committed files detected. The migration and prompt storage logic are well-contained and the UI changes are appropriately guarded.
Automated review by pr-reviewer v0.38.0 | Full Review | Claude Sonnet 5 | tracking id
r-61ffe1-c8a8e9This is an AI-generated review and may contain mistakes.
Status: ✅ Completed
✅ Verdict: Approve — The prompt-capture design is structurally sound (single resolution helper prevents drift), migration and template changes are safe and well-tested, and the only notable concern — multiplied storage growth per fan-out config — is already acknowledged and cross-referenced to the storage-tracking follow-up.
Overall this is a clean, well-scoped change that does what it says: capture the exact review-pass input at fan-out time and surface it on the comparison page. The design rationale in the migration comments and code comments is unusually thorough, and the approach of centralizing prompt resolution in
ResolveReviewSystemPrompt(used by both the backends and the runner) is exactly right — it structurally prevents the stored copy from drifting from what was actually sent, which was the main risk in this kind of feature.Things I looked at specifically:
Correctness of prompt capture
fanOutnow rendersreqviabuildReviewRequest, then callsResolveReviewSystemPrompt(req)andBuildUserMessage(req)before invokingrunOne. SincerunOne/Reviewalso call these exact same helpers against the identicalreqvalue, the stored fields are guaranteed to match the sent fields — no logic duplication that could diverge. Good.recordConfigResultstampsevent.PromptSystem/PromptUserunconditionally, including on failure, matching the PR's stated intent ("failed runs keep their input too"). This makes sense since the prompt is rendered before the network call.runOnereturns an error before the request is actually sent to the network (e.g., "no backend registered for provider" inrunOne), the stored prompt is still recorded, even though nothing was actually transmitted. The doc comments already partially acknowledge this ("usually sent"), so this seems like a deliberate, accepted tradeoff rather than an oversight — worth confirming that's intentional but not a blocking issue.Migration
''with backfill-freeADD COLUMN ... DEFAULT ''is the right call for a TEXT column of unknown historical volume — no full-table rewrite needed on modern Postgres since the default is constant, and old/failed rows read back empty cleanly. Down migration is trivial and correct.Storage / performance concern
Template / dashboard
{{if .PromptUser}}guard correctly hides the whole section when empty;{{if .PromptSystem}}further guards the inner system-prompt block for cases where only user message exists (shouldn't happen given resolution logic always returns non-empty system, but harmless defensive code).html/templateauto-escapes{{.PromptSystem}}/{{.PromptUser}}by default, so arbitrary diff/file content rendered into an admin-only page is safe from XSS as long as the dashboard continues to usehtml/template(which it appears to, givenrender()is templated HTML elsewhere in this file).ReviewBody/diff handling elsewhere in the dashboard, so not a new risk introduced by this PR specifically, and the page is presumably gated byRequireAdmin.Test coverage
compare_render_test.gocovers both the "shows" and "hides" cases for the template, which is the primary rendering behavior that could regress.runner_test.go'sTestRunnerStoresPromptInputcovers both completed and failed paths, verifying the resolved system prompt and that the diff shows up in the user message. This directly covers the main risk (failure path losing the prompt).finish_reasonadded previously, so the risk of the SQL wiring being subtly wrong is low, and it will be caught quickly in an integration/smoke environment.SystemPromptOverrideset to confirm the per-row independence claim (that two configs in a fan-out get different persisted prompts when one has an override) — this is the crux of the "each config row gets its own copy" design claim. Might be worth a quick unit test assertingconfigA.PromptSystem != configB.PromptSystemwhen overrides differ, since correctly capturing this divergence is the whole point of doing this at fan-out instead of once. Minor gap, not blocking.Readability
runOneintobuildReviewRequest+runOneis a clean way to share the request construction without duplicating field-by-field logic, and it's clearly commented as to why.No breaking changes to existing behavior; migration is additive and backward compatible (default
'',IF NOT EXISTSguards). No security issues beyond the general note above about admin-page content escaping, which is already covered by Go's template auto-escaping.This is a solid, well-tested, appropriately-scoped PR. My only substantive ask would be some acknowledgement of the storage growth mitigation plan (even just a follow-up ticket reference), but the PR description already gestures at this via the #78 storage page, so I'm not going to block on it.
📚 Context used in this review
Project guidance files:
CLAUDE.md