Skip to content

feat(gmail): add --include-attachments to messages search - #962

Merged
steipete merged 7 commits into
openclaw:mainfrom
ronny-rentner:feat/gmail-include-attachments
Aug 9, 2026
Merged

feat(gmail): add --include-attachments to messages search#962
steipete merged 7 commits into
openclaw:mainfrom
ronny-rentner:feat/gmail-include-attachments

Conversation

@ronny-rentner

@ronny-rentner ronny-rentner commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds --include-attachments to gmail messages search. With it, each result carries its attachment metadata — filename, size, mimeType (and the attachmentId in JSON) — so you can see what a message holds without switching to --include-body.

Today the only way to see a message's attachments in search results is --include-body, which also decodes and renders the full body. --include-attachments gives just the attachment list.

  • New flag --include-attachments (env GOG_GMAIL_INCLUDE_ATTACHMENTS).
  • Emits the existing attachmentOutput shape (the same one --include-body already produces), so the JSON is purely additive — no existing field changes.
  • Text output gains an ATTACHMENTS column (filename (mimeType, size)); JSON gains the attachments array (with the attachmentId).
  • Fetches format=full — the same complete fetch gmail get / thread / drafts already use — so attachments at any MIME nesting depth are listed. The body data is transferred but never rendered.

Motivation

Lets a caller enumerate what a message carries — and, from the JSON, get the attachmentId to download — without switching to --include-body.

User-facing changes

  • New flag --include-attachments on gmail messages search.
  • New env var GOG_GMAIL_INCLUDE_ATTACHMENTS (mirrors the existing GOG_GMAIL_INLINE_MAX_BYTES pattern).
  • When set: an ATTACHMENTS column in text output and an additive attachments array in JSON.

Testing

  • TestExecute_GmailMessagesSearch_IncludeAttachments: lists an attachment nested several MIME levels down (proving the full fetch), does not render the body, and asserts the fetch uses format=full with no capping parts mask; the default search lists neither.
  • make ci clean (fmt / lint / deadcode / tests / docs).

Verification

Redacted terminal output from the built binary against a live Gmail account (ids masked, attachmentId truncated; sizes, mimeType, sender, subject verbatim).

Text output — the ATTACHMENTS column:

$ gog gmail messages search "from:purchase-noreply@twitch.tv has:attachment after:2026/08/01" --include-attachments
ID                THREAD            DATE              FROM                        SUBJECT          LABELS                         ATTACHMENTS
19fd72XXXXXXXXXX  19fd72XXXXXXXXXX  2026-08-06 14:41  purchase-noreply@twitch.tv  Kaufbestätigung  UNREAD,CATEGORY_UPDATES,INBOX  invoice_EU-DEXXXXXXX.pdf (application/pdf, 21.1 KB)

JSON — same metadata plus the per-fetch attachmentId:

$ gog gmail messages search "from:purchase-noreply@twitch.tv has:attachment after:2026/08/01" --include-attachments --json | jq '.messages[0].attachments'
[
  {
    "filename": "invoice_EU-DEXXXXXXX.pdf",
    "size": 21609,
    "sizeHuman": "21.1 KB",
    "mimeType": "application/pdf",
    "attachmentId": "ANGjdJ_5g7aKXXXX… (426 chars)"
  }
]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54c58df304

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

items, err := fetchMessageDetails(ctx, svc, messages, idToName, loc, c.IncludeBody, c.BodyFormat)
items, err := fetchMessageDetails(ctx, svc, messages, idToName, loc, c.IncludeBody, c.BodyFormat, c.IncludeAttachments)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render attachments in non-JSON search output

When --include-attachments is used with the default table output or --plain, this path fetches and populates messageItem.Attachments, but the later call to gmailMessageColumns(c.IncludeBody, c.Full) never reads that field. The flag therefore performs the additional full-format API requests while producing exactly the same stdout as a search without the flag. Pass the option into the presentation layer and render the metadata, or explicitly restrict and document the flag as JSON-only.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 7, 2026
@clawsweeper

clawsweeper Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

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

ClawSweeper review

What this changes

The PR adds an opt-in Gmail message-search flag and environment variable that return attachment metadata in table and JSON output without rendering decoded message bodies.

Merge readiness

⚠️ Ready for maintainer review - 1 item remains

Keep open: current main does not implement this opt-in Gmail search capability, and the final patch has resolved the prior table-output safety finding. The supplied redacted live terminal output is sufficient real-behavior proof; no actionable correctness or security finding remains.

Priority: P2
Reviewed head: d6a5e35ebd186e450f6628b212ba2dc2a99ea2e7

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) A focused, tested opt-in feature with sufficient live CLI proof and no remaining actionable review finding.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (terminal): The PR body contains redacted after-fix terminal output from a live Gmail account showing the new table column and JSON attachment metadata.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The PR body contains redacted after-fix terminal output from a live Gmail account showing the new table column and JSON attachment metadata.
Evidence reviewed 5 items Current main lacks the feature: Current main has the existing body-only detail option and does not define or pass an attachment-only search option.
Feature wiring and default preservation: The PR adds an explicit flag, requests full Gmail details only when body or attachments are requested, and leaves default searches on the metadata-only path.
Prior finding repaired: Gmail-provided filenames and MIME values are mapped to safe spaces for control characters before table rendering, with coverage for tabs, newlines, carriage returns, and escape characters.
Findings None None.
Security None None.

How this fits together

Gmail search lists message IDs, optionally fetches each message’s details, then formats results as a table or JSON. This option switches the detail fetch to include attachment metadata and passes it to the existing output surfaces.

flowchart LR
  A[Gmail search query] --> B[Message ID list]
  B --> C[Optional full detail fetch]
  C --> D[Attachment collection]
  D --> E[Table or JSON output]
Loading

Before merge

  • Complete next step (P2) - No focused repair remains; this needs routine check completion and maintainer merge review.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Patch surface 200 added, 18 removed across 6 files The change is limited to the Gmail search path, generated command reference, and targeted coverage.
Implementation versus tests implementation +47, tests +152, docs +1 The small opt-in implementation is accompanied by substantially more focused regression coverage.

Technical review

Best possible solution:

Land the narrow opt-in option after the pending checks finish, retaining metadata-only fetches and unchanged output for existing searches.

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

Not applicable as a defect report; the PR includes redacted after-fix terminal output from a live Gmail account for both table and JSON behavior.

Is this the best way to solve the issue?

Yes. It reuses the existing attachment output shape, preserves default search behavior, and confines the new fetch behavior to an explicit option.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 3fecbc007a5b.

Labels

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body contains redacted after-fix terminal output from a live Gmail account showing the new table column and JSON attachment metadata.
  • 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 (terminal): The PR body contains redacted after-fix terminal output from a live Gmail account showing the new table column and JSON attachment metadata.
  • remove rating: 🧂 unranked krab: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove status: 📣 needs proof: Current PR status label is status: 👀 ready for maintainer look.
  • remove merge-risk: 🚨 security-boundary: Current PR review selected no merge-risk labels.

Label justifications:

  • P2: This is a bounded opt-in Gmail search enhancement rather than an urgent broken workflow.
  • 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 (terminal): The PR body contains redacted after-fix terminal output from a live Gmail account showing the new table column and JSON attachment metadata.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body contains redacted after-fix terminal output from a live Gmail account showing the new table column and JSON attachment metadata.

Evidence

What I checked:

Likely related people:

  • chrischall: Authored the recent current-main change to the Gmail message listing path. (role: recent Gmail listing contributor; confidence: medium; commits: 3c9466af5171; files: internal/cmd/gmail_messages.go)
  • Ronny Rentner: Authored merged Gmail attachment configuration work adjacent to the attachment metadata surface. (role: prior Gmail attachment contributor; confidence: medium; commits: c7d66df6a99a; files: internal/cmd/gmail_attachment.go)

Rank-up moves

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

  • Allow the two pending test/platform checks to complete on the reviewed head.

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.

History

Review history (13 earlier review cycles; latest 8 shown)
  • reviewed 2026-08-07T22:01:46.611Z sha d57234f :: needs real behavior proof before merge. :: [P2] Do not silently omit deeply nested attachments | [P2] Expose MIME type and identifier in text output
  • reviewed 2026-08-08T03:01:07.784Z sha d57234f :: needs real behavior proof before merge. :: [P2] Avoid silently omitting deeply nested attachments | [P2] Render MIME type and identifier in text output
  • reviewed 2026-08-08T04:40:59.083Z sha d57234f :: needs real behavior proof before merge. :: [P2] Fetch attachment metadata at every MIME depth | [P2] Expose the advertised metadata in text output
  • reviewed 2026-08-08T04:48:33.173Z sha 14ae959 :: needs real behavior proof before merge. :: [P1] Avoid fetching raw bodies for attachment-only searches
  • reviewed 2026-08-08T05:26:25.627Z sha eb83a8b :: needs real behavior proof before merge. :: [P1] Avoid fetching raw bodies for attachment-only searches | [P2] Emit download-capable IDs in text output
  • reviewed 2026-08-08T07:38:14.007Z sha d92c681 :: needs real behavior proof before merge. :: [P1] Keep attachment-only searches from downloading body data | [P2] Print complete attachment IDs in text output
  • reviewed 2026-08-08T09:11:39.825Z sha 64754e1 :: needs real behavior proof before merge. :: [P1] Avoid fetching body data for attachment-only searches | [P2] Keep download-capable attachment IDs in text output
  • reviewed 2026-08-08T16:04:55.572Z sha 64754e1 :: found issues before merge. :: [P2] Sanitize attachment metadata before table rendering

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 7, 2026
@clawsweeper clawsweeper Bot added merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. and removed merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 7, 2026
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. label Aug 8, 2026
@ronny-rentner

ronny-rentner commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

On the "render MIME type and identifier in text output" finding: mimeType is now in the ATTACHMENTS column; the attachmentId deliberately is not. Two reasons:

1. The full id doesn't fit a table column. Gmail's attachmentId is ~426 bytes. Dropping that into a cell blows the table apart — it's unusable next to filename, mimeType, and size.

2. A short id isn't feasible either. gogcli already uses a short (first-8-char) form of the attachmentId to tag saved filenames, so the obvious idea was to show that same short id in the column. But the attachmentId is a per-request opaque token — Gmail mints a fresh ~426-char value on every messages.get, and all of them stay valid to download by. Verified against a live account, three fetches of the same message returned three different ids (leading bytes shown, the rest masked):

ANGjdJ-pXXXX…
ANGjdJ9oXXXX…
ANGjdJ9vXXXX…

and an id captured in one call still downloaded fine in a separate later call. Because the id changes on every call, its short form changes on every search too — it would be neither stable nor a match for a later download's filename, so it conveys nothing a caller can act on.

So the column shows filename (mimeType, size). The full per-fetch id stays in --json (valid for that fetch), which is where retrieval belongs; the stable, compact reference is the 0-based index in the stacked #963.

@clawsweeper clawsweeper Bot added 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. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 9, 2026
@steipete
steipete merged commit 9842e64 into openclaw:main Aug 9, 2026
12 checks passed
@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. label Aug 9, 2026
@steipete

steipete commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Landed in 9842e64b7682e748dd9a34c3df361fbb8c8298ca; maintainer changelog follow-up is 1e9a712c9.

Maintainer fix before landing:

  • Gmail-controlled attachment filenames and MIME values now have tabs, line breaks, and other control characters replaced at the text/TSV presentation boundary. JSON metadata remains unchanged.

Proof:

  • GOCACHE=<lane-private-cache> make ci — passed the complete local gate.
  • go test ./internal/cmd -run 'TestExecute_GmailMessagesSearch_IncludeAttachments|TestGmailPresentationSchemas' -count=1 — passed, including the control-character regression case.
  • Signed branch binary exercised read-only against a real Gmail account: the default search header had no ATTACHMENTS column; --include-attachments added it; flag and GOG_GMAIL_INCLUDE_ATTACHMENTS=1 JSON each returned one real attachment with attachmentId, filename, mimeType, size, and sizeHuman; no decoded body field was emitted.
  • Final Codex autoreview: clean, no accepted/actionable findings.
  • GitHub CI run 31291954680 passed Linux, worker, Windows, and Darwin jobs; Docker run 31291954693 passed.

Thanks @ronny-rentner for the careful iteration and live evidence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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