feat(review): comment-only review event policy (advisory reviews)#1479
Conversation
Adds a per-agent `review_event_policy` option (`all` | `comment-only`) on agent_configs, mirroring the update_channel pattern. Under `comment-only` the CreatePRReview tool layer deterministically downgrades every review submission (APPROVE / REQUEST_CHANGES / COMMENT) to a non-blocking COMMENT whose body leads with a human-readable advisory verdict line — developers keep approval authority; the agent contributes information only. - New catalog module src/config/reviewEventPolicy.ts (policy enum, resolver, advisory preamble, applyReviewEventPolicy) — single source of truth. - Migration 0060 adds the nullable review_event_policy column; NULL inherits the default `all`, so existing projects are byte-identical. - Config surface: tRPC agentConfigs.create/update, CLI `cascade agents create/update --review-event-policy`, dashboard Select (rendered only for the review agent, NULL persisted for the default). - Enforcement covers both engine families: in-process gadgets resolve the policy from SessionState; the cascade-tools subprocess reads CASCADE_REVIEW_EVENT_POLICY (injected only when comment-only). - review.eta forks on commentOnlyReview: the agent still passes its true verdict as `event`; the tool downgrades it. - check-suite-success re-review dedup now counts reviewer-persona COMMENTED reviews as "already reviewed at HEAD" when the policy is comment-only (config check, no body parsing) — otherwise every green check suite would re-review the same commit forever. Default-policy behavior is pinned unchanged by regression tests. - Fixes a latent CLI bug: the review sidecar recorded the requested event instead of the submitted one. Note: pr-ready-to-merge moves the PM card only on an approved review, which in comment-only mode comes from a human — intended. Projects using this mode should keep the respond-to-review trigger disabled (reviewer COMMENTED reviews dispatch it when enabled). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
APPROVE — a clean, well-architected review_event_policy feature that faithfully mirrors the update_channel pattern end-to-end, enforces the policy in the tool layer (not prompt-only) across both engine families, and is byte-identical to current behavior under the default (all) policy. I traced every layer and ran the new/changed suites (config catalog, core coercion, gadget + CLI, check-suite dedup, secretBuilder, configMapper, prompt fork, web guard) — 188 tests pass.
What I verified
- Hard enforcement is complete and single-application.
applyReviewEventPolicyis invoked exactly once per path — CLI (create-pr-review.tsviaCASCADE_REVIEW_EVENT_POLICY) and in-process gadget (CreatePRReview.tsviagetProject()/getAgentType()). The two paths are mutually exclusive per engine, so there's no double-downgrade.CreatePRReviewis the only approval vector available to the agent, socomment-onlygenuinely cannot APPROVE/REQUEST_CHANGES. - Re-review dedup is necessary and correct. Counting reviewer-persona
commentedreviews only undercomment-only(countCommented) is what prevents every green check-suite from re-reviewing the same HEAD forever, while the SHA guard (latestReview.commitId === headSha) still allows re-review on new commits. Implementer-persona reply-acks stay excluded via the login filter. Default-policy behavior is preserved (regression pin present). - No accidental card auto-advance.
pr-ready-to-mergecounts onlyapproved/changes_requestedGitHub review states, so the agent's advisorycommentedreviews neither approve nor block — developer approval authority is preserved, exactly as the PR claims. - Default path is untouched.
all→ no env injected,promptContext.commentOnlyReview=false(else-branch is identical to the original template),finalBody === params.body,event === params.event. The sidecar "requested→submitted" change is a no-op underalland the intended fix undercomment-only. - Migration
0060journal entry is consistent (idx 60, monotonicwhen, tag matches filename);ProjectConfigsurfacesagentReviewEventPoliciesviaz.infer; the env var rides the same provenprojectSecretschannel asCASCADE_AGENT_TYPE/sidecar vars.
Questions / non-blocking considerations
respond-to-review+comment-onlyinteraction.pr-review-submittedmatches on any non-approvedreview, so undercomment-onlyevery advisory review can dispatchrespond-to-review(if enabled) → new commit →check-suite-successre-review → repeat, with no agent-side "approve" to terminate (only a developer can). You've correctly documented "keep respond-to-review disabled" in both the PR notes and the dashboard help text, and the underlying interaction pre-dates this PR (a genuine reviewer COMMENT review already dispatches respond-to-review today). Flagging only to confirm the doc-only mitigation is the intended tradeoff rather than a defensive guard (e.g. skipping reviewer-persona advisory COMMENTED reviews inpr-review-submittedwhen the project iscomment-only).- Docs. This feature mirrors
update_channel, which has a dedicatedCLAUDE.mdsection, but no equivalent section was added here. Given the operational nuance above, a short parallel section (policy table + the respond-to-review caveat) would help operators. Minor.
🕵️ claude-code · claude-opus-4-8 · run details
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
APPROVE — a clean, complete, well-tested feature that adds a per-agent review_event_policy (all default | comment-only). I traced every enforcement path and downstream consumer; the implementation is correct and the default (all, NULL column) is byte-identical to current behavior.
What I verified
Enforcement is deterministic and un-bypassable. The coercion lives in one place (applyReviewEventPolicy → createPRReview core). Both agent-facing entry points route through it:
- in-process gadget (LLMist) resolves the policy from SessionState (
getProject()/getAgentType()); - the
cascade-tools scm create-pr-reviewsubprocess (native-tool, the default engine) readsCASCADE_REVIEW_EVENT_POLICY, injected byaugmentProjectSecretsalongside the provenCASCADE_AGENT_TYPE/CASCADE_PM_TYPEsecrets.
There are exactly two callers of the core createPRReview and both pass eventPolicy; no agent-facing code calls githubClient.createPRReview directly, so an agent genuinely cannot APPROVE/REQUEST_CHANGES under comment-only.
Downstream consumers of the submitted event/body are safe. The "record SUBMITTED, not requested" fix flows correctly into completion.ts (gates on reviewUrl presence, not the event value), sidecarManager → recordReviewSubmission, and agent-pm-poster.formatReviewForPM (event only drives the emoji/header label). The PM comment now correctly reflects the advisory COMMENT with the "Advisory verdict: …" preamble.
Re-review dedup is correct. check-suite-success counts reviewer-persona commented reviews as "already reviewed at HEAD" only under comment-only, then compares commitId === headSha — so a stale-SHA advisory review still re-reviews (regression-pinned), and the default policy still ignores COMMENTED reviews.
Rest of the wiring is consistent with the update_channel pattern it mirrors: migration 0060 + journal (numbering/version/monotonic when all check out), config mapper validation (unknown/NULL skipped), tRPC + CLI + dashboard round-trip (listAgentConfigs selects the new column so the UI displays the saved value), and the review.eta prompt fork. CI is 7/7 green.
Notes (non-blocking)
comment-only+respond-to-reviewloop:pr-review-submitted.tsdispatchesrespond-to-reviewoncommentedreviews (not justchanges_requested), and under this mode every reviewer-persona submission is a COMMENT — so enabling both would self-loop. This is real but well-mitigated:respond-to-reviewisdefaultEnabled: false, so it requires opting into two non-default settings, and the interaction is called out in both the dashboard help text and the PR description, with budget caps as the backstop. Worth keeping in mind for a future hard guard, but documentation is a reasonable choice here.- Optional docs: given the loop-prevention implications, a short CLAUDE.md section mirroring the existing "Agent update channel" write-up would help operators. Not required — the feature is already documented via the catalog module JSDoc, the migration comment, the CLI flag, and the dashboard help text.
🕵️ claude-code · claude-opus-4-8 · run details
Summary
Adds a configurable per-agent
review_event_policy(alldefault |comment-only). Undercomment-only, the review agent can never APPROVE or REQUEST_CHANGES on a PR — every review is submitted as a non-blocking COMMENT whose body leads with a human-readable advisory verdict line:Developers keep verdict authority; the agent contributes information only. The default (
all, NULL column) is byte-identical to current behavior.Design
Mirrors the
update_channelpattern end-to-end:src/config/reviewEventPolicy.tsis the single source of truth: policy enum + Zod schema,resolveReviewEventPolicy(),buildAdvisoryPreamble(), andapplyReviewEventPolicy()(the coercion). Deliberately no machine-readable markers and no regex parsing of review bodies — every internal decision is a config check.0060_agent_config_review_event_policy.sqladds the nullable column; the config mapper validates values against the catalog and surfacesProjectConfig.agentReviewEventPolicies(NULL/unknown silently skipped so a stale column never breaks config load).agentConfigs.create/update, CLIcascade agents create/update --review-event-policy, dashboard Select on the agent Engine tab (rendered only for the review agent; the default persists as NULL).getProject()/getAgentType());cascade-tools scm create-pr-reviewsubprocess readsCASCADE_REVIEW_EVENT_POLICY, injected byaugmentProjectSecretsonly when the policy iscomment-only.The agent keeps passing its true verdict as
event; the shared core downgrades it deterministically.review.etaforks oncommentOnlyReview(severity→event mapping, Review Decision, rules). "If the PR is good, approve it" is replaced with the advisory contract in this mode.check-suite-successcounts reviewer-persona COMMENTED reviews as "already reviewed at HEAD" only when the project's review policy is comment-only; otherwise every green check suite would re-review the same SHA forever. Default-policy behavior and the implementer reply-ack exclusion are pinned by regression tests.Operational notes
pr-ready-to-mergemoves the PM card only on anapprovedreview — in comment-only mode that approval comes from a developer, which is the feature intent.Testing
TDD throughout — tests written first at every layer:
npm run lint,npm run typecheck, webtsc --noEmitclean; full unit suite green (599 files / 10,799 tests); migration 0060 + repository integration tests verified against a fresh Postgres 16.🤖 Generated with Claude Code