Skip to content

feat(gmail): add drafts reply, reply-all, and forward - #980

Merged
steipete merged 4 commits into
mainfrom
triage/t8b-pr977
Aug 11, 2026
Merged

feat(gmail): add drafts reply, reply-all, and forward#980
steipete merged 4 commits into
mainfrom
triage/t8b-pr977

Conversation

@steipete

Copy link
Copy Markdown
Collaborator

Lands #977 with @malob's three commits preserved as authored, plus a
maintainer changelog commit.

Review verdict from the dedicated lane: the design is sound — shared compose
builders, address-aware recipient parsing across all commands, and draft-only
semantics that reuse the send-side flags. Verified in review: dry-runs emit the
correct gmail.drafts.* operations, --gmail-no-send permits drafts while
still blocking gmail reply, the agent-safe binary permits draft composition
while readonly rejects it, and full CI/autoreview pass.

Provider-backed proof was completed against the approved test mailbox with the
branch binary (draft created, verified, deleted; seed message trashed):

seed Message-ID : <CAOHaX8N…@mail.gmail.com>
draft In-Reply-To: <CAOHaX8N…@mail.gmail.com>   (exact match)
draft References : <CAOHaX8N…@mail.gmail.com>   (exact match)
draft Subject    : Re: drafts-proof-seed-t8b-977
draft threadId   : same as seed
labels           : DRAFT only — nothing was sent

One rail proved itself unprompted: replying to a self-addressed message
correctly fails closed with "reply has no recipients after applying recipient
changes" — self-exclusion works and the validator refuses an empty recipient
list rather than guessing.

Closes #977. Thanks @malob.

malob and others added 4 commits August 10, 2026 19:56
Split the message-build half of gmail reply/reply-all/forward into pure builders (buildReplyComposeMessage/buildForwardComposeMessage) that take an already-acquired *gmail.Service, plus service-free resolveReplyInputs/resolveForwardInputs that resolve body/note exactly once and validate. The Run methods now orchestrate resolve -> dry-run -> acquire -> build -> finalize, so the acquire gate and finalize step are no longer baked into the builders.

Extract composeSignatureOptions (the shared signature flags + signatureRequested/validateSignatureOptions/resolveComposeSignature methods) and embed it into GmailSendCmd and GmailReplyOptions, removing the throwaway GmailSendCmd the reply path constructed just to borrow those methods. Extract a GmailForwardOptions embed from GmailForwardCmd.

No behavior change: CLI flags are byte-identical, and validation order, dry-run output, error wrapping, and finalize are unchanged; the existing reply/reply-all/forward/send/signature tests pass with assertions unchanged. This sets up reuse by upcoming drafts reply/reply-all/forward commands, which will share resolve+build and finalize with Drafts.Create under the non-send service gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `gmail drafts reply`, `drafts reply-all`, and `drafts forward`, giving the drafts surface full flag/ergonomic parity with the send-side reply/reply-all/forward commands. They embed the same options structs and reuse the shared resolve/build helpers, differing only at finalize: they save a draft (Drafts.Create) instead of sending. Drafts use the non-send service gate, so they work under --gmail-no-send, matching gmail drafts create.

`drafts forward` allows an addressless draft (no --to), matching drafts create and Gmail's UI. The recipient requirement is resolved explicitly per call, which also aligns gmail forward's required---to handling with send/reply (runtime check + explanatory help) while keeping the MIME missing-To backstop on the send path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gmail send, forward, and drafts create/update parsed --to/--cc/--bcc with naive comma-splitting, while reply already parsed them as addresses. The splitting mangled quoted display names ("Smith, John" <john@example.com>) wherever the fragment list was consumed directly: --track counted such a recipient as two and refused to send, --track-split built one message per fragment, and dry-runs reported the broken fragments. Unparseable input was not rejected: send transmitted messages with garbage or empty To headers, and RFC 5322 group syntax ("undisclosed-recipients:;", which parses to zero addresses without an error) was silently dropped or silently saved.

Route every compose command through shared address-aware parsing (parseComposeRecipients -> mail.ParseAddressList), with reply's within-flag case-insensitive dedup applying everywhere. Recipients are parsed service-free before the dry-run, so the dry-run reports the same lists the built message carries; malformed input and non-empty flags that parse to zero recipients now fail fast with a flag-named error and no API call, on every compose command. A draft update without --to keeps the existing draft's To header leniently (verbatim if it does not parse), so legacy drafts stay editable. Nameless addresses whose bare form is not valid on the wire (quoted local parts) are re-quoted instead of silently emitted broken. The tracking pixel identity is now always the bare email address, independent of how the recipient was typed. Includes final-review test and comment polish across the compose paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@clawsweeper

clawsweeper Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 message-delivery 🚨 Merging this PR could drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Aug 11, 2026
@clawsweeper

clawsweeper Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed August 10, 2026, 11:16 PM ET / August 11, 2026, 03:16 UTC.

ClawSweeper review

What this changes

Adds Gmail draft-only reply, reply-all, and forward commands, refactors shared compose helpers, and parses compose recipients as RFC-aware mailbox lists.

Merge readiness

⚠️ Ready for maintainer review - 3 items remain

Keep open: this active collaborator-owned PR adds a capability absent from current main, with provider-backed draft creation proof and no discrete correctness finding in the reviewed patch.

Priority: P2
Reviewed head: de2fd56ce9720fc3194a4eea31b1df3319dff6d4
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) Strong real-provider evidence and focused regression coverage support the feature; final cross-platform validation remains the material merge gate.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): The PR body provides provider-backed after-fix output showing drafts created under no-send, checked for thread headers and DRAFT-only state, then deleted.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (live_output): The PR body provides provider-backed after-fix output showing drafts created under no-send, checked for thread headers and DRAFT-only state, then deleted.
Evidence reviewed 5 items Current main lacks the requested commands: Current main's drafts command surface contains list/get/delete/send/create/update only; this branch adds the three draft compose leaves, so the central feature is not already implemented.
Draft implementation preserves the send/draft boundary: The new reply and forward paths reuse shared builders but finalize through Gmail Drafts.Create after the non-send service gate.
Compatibility coverage: The branch adds byte-parity, no-send, reply-threading, addressless-forward, malformed-recipient, and generated safety-profile coverage around the shared compose paths.
Findings None None.
Security None None.

How this fits together

Gmail CLI compose commands turn user inputs into MIME messages, then either send them or save them as Gmail drafts. Safety profiles and no-send guards decide which operations may use the Gmail service.

flowchart LR
  A[CLI compose command] --> B[Validate body and recipients]
  B --> C[Shared message builder]
  C --> D{Operation type}
  D --> E[Send message]
  D --> F[Save Gmail draft]
  G[Safety profile and no-send guard] --> D
Loading

Decision needed

Question Recommendation
After the current platform checks finish, should this collaborator-owned branch be merged as the landing vehicle for the draft-compose feature represented by #977? Merge after checks pass: Land this branch as the reviewed integration of the contributor feature and its release-note context.

Why: Collaborator-authored PRs are not eligible for automated cleanup, and the final landing choice remains maintainer authority despite strong implementation and proof evidence.

Before merge

  • Resolve merge risk (P1) - The shared recipient parser and compose refactor also affect existing send, forward, and draft-update flows; platform checks should complete before merge.
  • Complete next step (P2) - This active collaborator PR requires normal maintainer landing review and completion of its current platform validation; no narrow automated repair is identified.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Patch surface 32 files; +2,835/-286 lines; core implementation +650, tests +1,965, docs/release notes +220 The three new commands reuse and modify established compose machinery, so the broad regression coverage is material to merge confidence.

Merge-risk options

Maintainer options:

  1. Complete cross-platform validation (recommended)
    Allow the in-progress test, macOS, and Windows checks to finish successfully before merging the shared compose refactor.

Technical review

Best possible solution:

Merge the draft-only compose paths while retaining the existing no-send and safety-profile boundaries and the new regression coverage for shared recipient handling.

Do we have a high-confidence way to reproduce the issue?

Not applicable: this PR adds a new capability, and its body includes a provider-backed after-fix validation run for the new Gmail draft flows.

Is this the best way to solve the issue?

Yes: reusing the existing compose builders and changing only finalization to Gmail Drafts.Create is the narrowest maintainable implementation path.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 6af89c8dd7cb.

Labels

Label changes:

  • add P2: This is a bounded Gmail workflow improvement with meaningful but non-urgent user impact.
  • add merge-risk: 🚨 compatibility: Shared recipient parsing and compose refactoring change behavior in existing send, forward, and draft-update commands.
  • add merge-risk: 🚨 message-delivery: The modified builders determine recipient headers, reply threading, and whether a compose operation sends or saves a draft.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides provider-backed after-fix output showing drafts created under no-send, checked for thread headers and DRAFT-only state, then deleted.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body provides provider-backed after-fix output showing drafts created under no-send, checked for thread headers and DRAFT-only state, then deleted.

Label justifications:

  • P2: This is a bounded Gmail workflow improvement with meaningful but non-urgent user impact.
  • merge-risk: 🚨 compatibility: Shared recipient parsing and compose refactoring change behavior in existing send, forward, and draft-update commands.
  • merge-risk: 🚨 message-delivery: The modified builders determine recipient headers, reply threading, and whether a compose operation sends or saves a draft.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body provides provider-backed after-fix output showing drafts created under no-send, checked for thread headers and DRAFT-only state, then deleted.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides provider-backed after-fix output showing drafts created under no-send, checked for thread headers and DRAFT-only state, then deleted.

Evidence

What I checked:

  • Current main lacks the requested commands: Current main's drafts command surface contains list/get/delete/send/create/update only; this branch adds the three draft compose leaves, so the central feature is not already implemented. (internal/cmd/gmail_drafts.go:19, 6af89c8dd7cb)
  • Draft implementation preserves the send/draft boundary: The new reply and forward paths reuse shared builders but finalize through Gmail Drafts.Create after the non-send service gate. (internal/cmd/gmail_drafts_compose.go:50, de2fd56ce972)
  • Compatibility coverage: The branch adds byte-parity, no-send, reply-threading, addressless-forward, malformed-recipient, and generated safety-profile coverage around the shared compose paths. (internal/cmd/gmail_drafts_compose_test.go:92, de2fd56ce972)
  • Feature-history ownership: Current-main history shows first-class Gmail replies introduced in commit ba350d6, with later reply-alias and draft-maintenance work in the same surface. (internal/cmd/gmail_reply_commands.go:23, ba350d6824ce)
  • After-fix provider proof: The PR body records a branch-binary run against an approved Gmail mailbox that created, inspected, and deleted drafts while checking thread headers, DRAFT-only labels, and no-send behavior.

Likely related people:

  • steipete: Peter Steinberger introduced first-class Gmail replies and has repeatedly maintained the draft and compose surface on current main. (role: introduced adjacent Gmail compose behavior; confidence: high; commits: ba350d6824ce, 6db9b084fd28; files: internal/cmd/gmail_reply_commands.go, internal/cmd/gmail_drafts.go)
  • ronny-rentner: Recent current-main work added reply alias selection in the same compose path. (role: recent adjacent contributor; confidence: medium; commits: 88eb36191a1c; files: internal/cmd/gmail_reply_commands.go, internal/cmd/gmail_drafts.go)
  • chrischall: Current-main history includes draft threading and update fixes in the changed drafts subsystem. (role: draft-maintenance contributor; confidence: medium; commits: 4985e2681e31, 22fbc35c7c18; files: internal/cmd/gmail_drafts.go)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Let the in-progress platform checks complete before landing.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@steipete
steipete merged commit cd965f1 into main Aug 11, 2026
14 checks passed
@steipete
steipete deleted the triage/t8b-pr977 branch August 11, 2026 03:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 message-delivery 🚨 Merging this PR could drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants