feat(annotate): preserve notes on structured approval#1092
Open
rNoz wants to merge 5 commits into
Open
Conversation
This was referenced Jul 20, 2026
rNoz
marked this pull request as ready for review
July 20, 2026 13:27
Adopt the grep convention for the strict annotate gate's exit codes: 0 = approved, 1 = negative human outcome (annotated/dismissed under --require-approval), 2 = the gate itself was misconfigured or could not start/deliver a decision. Previously all usage/startup/validation failures shared exit 1 with "reviewer did not approve", so callers could not tell a denied review from a broken gate. - parseStrictAnnotateOptions failures (bad flag combos, strict flags outside annotate --gate --json) now exit 2 - --result-file preflight failures (missing parent, pre-existing or dangling-symlink destination) now exit 2 - post-decision publication failures (destination raced into existence, hard links unavailable, stdout write failure) now exit 2: they deliver no decision record at all, so the code's own fail-closed handling presents them as environment errors, never as a reviewer outcome -- and never approval, since only 0 means approved - decision outcomes keep 0/1 exactly as before; signal deaths keep 128+n - document the contract in AGENTS.md and the annotate-gates guide Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
Maintainer-decided follow-ups on the approve-with-notes feature:
- Revert the Mod+Enter rebind: with annotations present, Mod+Enter means
Send Feedback exactly as before, on every transport. Approve-with-Notes
stays available via the header button and its confirmation dialog. The
shortcut registry description ("approves when there are no annotations,
sends feedback when there are") is accurate again.
- Approve now includes feedback based on hasFeedbackToSend (not
hasFeedbackContent), so notes already delivered through the annotate
agent terminal are not re-sent as approval notes.
- getAnnotateApprovedWithNotesPrompt: spread vars before the context
defaults so the message-annotation path's `context: undefined` cannot
clobber them and leave a literal {{context}} in custom templates; add a
test pinning the message case with a custom template.
- copilot-last now passes approvalNotesSupported like the annotate and
annotate-last sites, so `copilot-last --gate --json` supports
notes-on-approve.
- Dedupe AnnotateOutcome: declared once in strict-annotate-result.ts,
re-exported by annotate-output.ts.
Also merges the updated strict-result branch (exit 2 for gate errors).
Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Built on top of #1091
Summary
Annotate gates now preserve complete reviewer feedback when their outcome transport can deliver notes with approval (saving another unnecessary turn with the Agent).
Bun and Pi advertise an explicit
approvalNotesSupportedcapability. Direct gated JSON, active OpenCode sessions, and Pi follow-up delivery opt in. When capable feedback exists, the UI presents Approve with Notes and confirms that the artifact will be approved, the notes are non-blocking guidance rather than a revision request, and the gate will close.Plaintext and hook-native output remain unchanged. Unsupported approval paths retain the existing warning and Send Feedback action. ;)
Fixes #930
Why
Annotate approval previously posted no body and resolved an empty approved decision. A reviewer who left small non-blocking notes had to choose between silently losing them and using Send Feedback, which means revise and reopen.
Structured transports can carry an approved decision and feedback together. Making that capability explicit lets the editor preserve notes without changing transports whose approval protocol has no feedback channel.
Changes
approvalNotesSupportedto Bun and Pi annotate server options and/api/planpayloads with a default offalse.--gate --jsonwithout--hook, OpenCode embedded sessions with a session ID, gated OpenCode CLI bridge launches with a session ID, and Pi annotation sessions./api/approve, preserve feedback and annotations, keep bodyless approval compatible, and reject malformed or wrong-type bodies with400without resolving.packages/editor/annotateSubmission.tsto compose complete feedback and build capability-aware approval requests.annotate.approvedWithNotesprompt while preserving the existing file or message context.apps/hook/server/annotate-output.ts, preserve plaintext and hook output byte-for-byte, and include nonempty feedback only in direct JSON approval.--hookremains unchanged.Validation
Focused rebased proof
bun run typecheck: passed.bun run build:hook: passed; Vite transformed 5,582 modules.bun run build:opencode: passed; bundled 690 embedded modules and 90 Node modules.bun run build:pi: passed; the build rebuilt the review app with 3,511 modules and the hook app with 5,582 modules.git diff --check: passed.In the integration branch combining the main PRs in-flight:
Live proof (ad hoc tests for this branch)
However, I have been using it for days as part of the integration branch, in real sessions (multi-cli harness, multi-project).
Some fake note, exited0, and its authoritative result was consumed.Global note!. Retainedvalidation/live/approval-ui-v2.stdout.logandvalidation/live/approval-ui-v2.result.jsoncontain the same JSON line. No final-UI process exit code was captured; automated tests pin the confirmation copy and prove approved exit0plus exact stdout/result bytes.These runs validate the direct structured transport and rebuilt UI. They do not validate native OpenCode or Pi delivery.
Example of usage E2E with the integration branch:
Screen.Recording.2026-07-20.at.15.44.17.mov
Manual testing gaps
Compatibility
The server capability defaults to
false, and bodyless/api/approveremains valid. Existing callers that do not opt in retain their current approval behavior.Direct plaintext approval still emits
The user approved.. Hook approval and close still emit empty stdout, while hook feedback still emits the existing blocking JSON.--hookis unchanged because its native approval path has no feedback channel.Send Feedback continues to mean revise and reopen. Approval notes are used only when the transport can represent "approved, with non-blocking guidance." The default approval-note prompt tells the receiving agent not to revise or reopen solely because notes were supplied unless the user explicitly requests it.
Prior art and overlap
GitHub overlap was refreshed on 2026-07-20. Open issue #930, “Approve with annotations,” directly requests this annotate approval-notes behavior. This PR fixes #930; no duplicate issue should be opened.
Issue #570 established annotate gate decisions, but approved results still lack notes. Closed issue #30 requested "Approve with comments"; the maintainer explicitly requested confirmation and shipped OpenCode plan support in 0.4.8.
#30 is plan-mode prior art, while #570 established gate decisions. This contribution implements #930 and provides annotate-gate transport parity for direct structured output, OpenCode, and Pi. It does not add Claude hook support.