feat(gmail): add --inline and --text content modes to gmail attachment#919
feat(gmail): add --inline and --text content modes to gmail attachment#919chrischall wants to merge 3 commits into
Conversation
gog gmail attachment previously only wrote the attachment to a local file
and returned the path — a dead end for remote callers (MCP clients) that
cannot read the host filesystem.
- --inline: returns the content base64-encoded in a contentBase64 field
(JSON) for attachments up to 3 MiB; larger ones keep the path-only
behavior with an explanatory `reason` instead of failing silently.
- --text: returns extracted text in a `text` field — PDFs via a pure-Go
text extractor (with a `note` when the PDF is scanned/image-only),
HTML with tags stripped, plain text verbatim. Unsupported types return
a clear `reason`. Mutually exclusive with --inline.
- Both modes resolve filename/mimeType from the message payload, with a
single-attachment fallback (Gmail attachment IDs are not stable across
API responses) and content sniffing when metadata is unavailable.
- Default behavior (no new flags) is unchanged: {path, cached, bytes}.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codex review: needs maintainer review before merge. Reviewed July 14, 2026, 10:32 AM ET / 14:32 UTC. Summary Reproducibility: not applicable. This PR fills a remote-client capability gap rather than fixing a violation of an existing documented contract. Current main’s path-only response clearly establishes the motivation. Review metrics: 2 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Land the bounded Do we have a high-confidence way to reproduce the issue? Not applicable: this PR fills a remote-client capability gap rather than fixing a violation of an existing documented contract. Current main’s path-only response clearly establishes the motivation. Is this the best way to solve the issue? Unclear: bounded inline bytes are the narrowest maintainable solution to the remote-path problem, while built-in text extraction is a larger optional parsing surface whose value depends on maintainer product and security intent. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against e4239f8c57a5. Label changesLabel justifications:
Evidence reviewedWhat I checked:
Likely related people:
What the crustacean ranks mean
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. How this review workflow works
Review history (5 earlier review cycles)
|
Address review on the --inline/--text attachment modes: - --plain output stays a stable one-record-per-line TSV: values containing tabs/newlines (extracted text) are emitted as a single Go-quoted field, with a regression test that round-trips multiline text via strconv.Unquote. - PDF parsing of attacker-controlled attachments is now bounded: a 10s timeout (on top of the existing 3 MiB input cap and panic recovery) so a pathological PDF cannot hang the command or a long-lived MCP server. - Adversarial fixture test: corrupt PDF input surfaces a clear reason instead of text, without panicking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@clawsweeper re-review — pushed 5e4cd61 fixing the --text --plain TSV contract (Go-quoted single field + regression test) and bounding PDF extraction with a 10s timeout + adversarial corrupt-PDF fixture; the PR body now includes redacted live CLI and MCP transcripts for default/--inline/--text and the fixed --plain output. |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
Address the remaining review P1: a goroutine timeout cannot terminate parser work, so repeated hostile PDFs could leave CPU/memory work behind in a long-lived MCP server. - gmail attachment --text now re-execs the gog binary as a hidden `gmail pdf-extract` child (PDF bytes on stdin, text on stdout) under exec.CommandContext, so the 10s timeout SIGKILLs the parser outright. - Child re-checks the input size cap; parent caps extracted-text output at 4 MiB (compressed streams can inflate past the input cap) and surfaces child stderr as the failure reason. - Tests spawn the real subprocess boundary (test binary dispatches via a TestMain hook): extraction through the child, a hung child killed on timeout, corrupt-PDF error propagation, direct child-command IO, and oversized-input rejection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@clawsweeper re-review — 1d59b5b replaces the goroutine timeout with a killable subprocess boundary: PDF parsing re-execs gog as a hidden |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
ClawSweeper status: review started. I am starting a fresh review of this pull request: feat(gmail): add --inline and --text content modes to gmail attachment This is item 1/1 in the current shard. Shard 0/1. This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking. Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted. |
Problem
gog gmail attachment <messageId> <attachmentId>downloads the bytes, writes them to a local file, and returns only that path. When the caller is a remote MCP client (Claude), the path is unreadable — every attachment download is a dead end.Changes
--inline— returns the content base64-encoded in acontentBase64JSON field for attachments up to 3 MiB (maxInlineAttachmentBytes). Oversized attachments keep the existing path-only behavior plus an explanatoryreason, so callers get a clear signal instead of silently unusable output.--text— returns extracted text in atextfield:github.com/ledongthuc/pdf(pure Go, panic-guarded); anoteflags scanned/image-only PDFs with no text layer.gmailcontent.StripHTMLTags).reasonpointing at--inline/--out.--inline(usage error).filename/mimeTypefrom the message payload, with a single-attachment fallback — Gmail attachment IDs are not stable across API responses, so an exact ID match can miss — and content sniffing (%PDF-header,http.DetectContentType) when metadata is unavailable.{path, cached, bytes}), so existing local workflows are unaffected.gog gmail getwas checked for the same gap: bodies are already inlined (BestBodyText); only attachments resolve to IDs, which these flags now cover.Tests
TDD throughout (12 new command-level tests against the httptest Gmail API): small-attachment inline round-trip, oversize fallback with reason, default-output unchanged, flag mutual exclusion, PDF extraction (hand-assembled minimal PDF fixture), image-only PDF note, HTML tag-stripping, plain-text verbatim, unsupported-type reason, oversize text reason, unstable-attachment-ID metadata fallback, no-metadata content sniffing.
make lint testpasses. Live-verified against a real Gmail account: default/--text/--inlineon a real PDF attachment (text extracted; base64 round-trips to the exact original bytes) — both via the CLI and end-to-end through the gogcli-mcp stdio server.🤖 Generated with Claude Code
Review response (5e4cd61)
--text --plainmultiline output fixed: plain output is now a stable one-record-per-line TSV — any value containing tabs/newlines (extracted text) is emitted as a single Go-quoted field. Regression test round-trips multiline text throughstrconv.Unquoteand asserts every row stays a 2-field record.pdfExtractTimeout), on top of the existing 3 MiB input cap and panic recovery, so a pathological PDF cannot hang the command or a long-lived MCP server. Added an adversarial corrupt-PDF fixture test (clearreason, no panic) and unit tests for the timeout path.--textis cleanly separable (gmail_attachment_text.go+ its tests) — happy to split it into a follow-up PR and land only--inlinehere if that's the preferred boundary.Live proof (redacted)
Real Gmail account, binary built from this branch, run against a real PDF receipt attachment (59,855 bytes). IDs, filename, and all personal/transaction content redacted; structure and byte counts are verbatim.
CLI
MCP (gogcli-mcp
gog_gmail_attachmentover stdio JSON-RPC,GOG_PATH→ this branch's binary)Review response (1d59b5b) — killable PDF extraction boundary
Addresses the remaining P1 ("the timeout only stops waiting; it cannot stop the goroutine performing the parse"):
--textnow runs PDF parsing in a separate killable process: the parent re-execs the gog binary as a hiddengmail pdf-extractchild (PDF bytes on stdin → text on stdout) underexec.CommandContext, so the 10 s timeout SIGKILLs the parser outright — no orphaned CPU/memory work in a long-lived MCP server, even for repeated hostile attachments.TestExtractPDFTextIsolated_KillsChildOnTimeoutcompletes in ~0.1 s with a 100 ms timeout — the child does not linger), corrupt-PDF stderr propagation, direct child-command IO, and oversized-input rejection.--texton the same real Gmail PDF extracts through the subprocess path;gog gmail pdf-extract < corrupt.pdfexits 1 withnot a PDF file: missing %EOF.If the maintainer still prefers landing
--inlinealone,--textremains cleanly separable — say the word and I'll split it.