feat: support Base attachment APIs#887
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR replaces record-field PATCH-based attachment updates with Base v3 attachment endpoints: append_attachments and remove_attachments, and adds a dedicated download flow. It introduces upload/download/remove shortcuts (batch upload via repeatable ChangesBase Attachment Operations Refactor
Sequence Diagram(s)sequenceDiagram
participant Client
participant BaseAPI as Base v3 API
participant DriveAPI as Drive API
participant FileSystem
rect rgba(100, 200, 255, 0.5)
Note over Client,FileSystem: Upload Attachment (New Flow)
Client->>FileSystem: Read local files
FileSystem-->>Client: File bytes
Client->>BaseAPI: POST /open-apis/base/v3/.../upload_file (multipart if >20MB)
BaseAPI-->>Client: file_token
Client->>BaseAPI: POST /open-apis/base/v3/.../append_attachments {rec_id, field_id, file_token, image_width?, image_height?}
BaseAPI-->>Client: Success
end
rect rgba(100, 255, 200, 0.5)
Note over Client,FileSystem: Download Attachment (New Flow)
Client->>BaseAPI: GET /open-apis/base/v3/.../get_attachments (optional file_token filter)
BaseAPI-->>Client: Attachment metadata list
Client->>DriveAPI: GET /drive/v1/medias/{file_token}/download
DriveAPI-->>FileSystem: Stream file bytes
Client->>FileSystem: Save to --output directory
FileSystem-->>Client: Local file path
end
rect rgba(255, 200, 100, 0.5)
Note over Client,BaseAPI: Remove Attachment (New Flow)
Client->>BaseAPI: POST /open-apis/base/v3/.../remove_attachments {rec_id, field_id, file_tokens: [...]}
BaseAPI-->>Client: Success
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #887 +/- ##
==========================================
+ Coverage 65.89% 65.91% +0.01%
==========================================
Files 518 520 +2
Lines 48945 49539 +594
==========================================
+ Hits 32254 32652 +398
- Misses 13921 14083 +162
- Partials 2770 2804 +34 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@shortcuts/base/base_execute_test.go`:
- Line 1968: The test name mismatches its assertions: Test currently named
"download attachment uses extra info" but asserts the returned map lacks
extra_info_used because downloadBaseAttachment never returns that key; either
rename the test to reflect what it actually verifies (e.g., "download attachment
includes extra parameter in request" /
TestDownloadAttachment_IncludesExtraParameterInRequest) or change the
implementation to include extra_info_used in downloadBaseAttachment's returned
map; update the test function name and any references accordingly or add the
extra_info_used field to the return value and adjust assertions to expect it.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3d05e80e-0681-499d-8c5e-52c95bff2812
📒 Files selected for processing (12)
shortcuts/base/base_dryrun_ops_test.goshortcuts/base/base_execute_test.goshortcuts/base/base_shortcuts_test.goshortcuts/base/record_upload_attachment.goshortcuts/base/record_upload_attachment_test.goshortcuts/base/shortcuts.goskills/lark-base/SKILL.mdskills/lark-base/references/lark-base-cell-value.mdskills/lark-base/references/lark-base-record-upload-attachment.mdskills/lark-base/references/lark-base-record.mdtests/cli_e2e/base/base_attachment_dryrun_test.gotests/cli_e2e/base/coverage.md
💤 Files with no reviewable changes (1)
- skills/lark-base/references/lark-base-record-upload-attachment.md
🚀 PR Preview Install Guide🧰 CLI updatenpm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@7d715ff932173123c0d4e8d3d15f7b2399fd5162🧩 Skill updatenpx skills add zgz2048/cli#codex/base-attachment-apis -y -g |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
shortcuts/base/record_upload_attachment.go (5)
770-784: ⚡ Quick winMove the existence pre-check before
DoAPIStreamto avoid wasted API calls.The
!overwriteexistence check at Lines 780–784 happens after the download HTTP request has already been initiated viaDoAPIStream. When the target file already exists and--overwriteis false, this causes an avoidable network round-trip, consumes API quota, and burns rate-limit budget — only to fail validation right after.Note that
planAttachmentDownloadTargets(Lines 695–699) already performs the same existence check at plan time, so the post-API repetition appears to be defensive against a TOCTOU between plan and execute. In that case, the re-check still has value, but it should run before the API call.♻️ Suggested reordering
func downloadBaseAttachment(ctx context.Context, runtime *common.RuntimeContext, item baseAttachmentDownloadItem, targetPath string, overwrite bool) (map[string]interface{}, error) { if _, err := runtime.ResolveSavePath(targetPath); err != nil { return nil, output.ErrValidation("unsafe output path: %s", err) } + if !overwrite { + if _, statErr := runtime.FileIO().Stat(targetPath); statErr == nil { + return nil, output.ErrValidation("output file already exists: %s (use --overwrite to replace)", targetPath) + } + } + query := larkcore.QueryParams{} if item.ExtraInfo != "" { query.Set("extra", item.ExtraInfo) } resp, err := runtime.DoAPIStream(ctx, &larkcore.ApiReq{ HttpMethod: http.MethodGet, ApiPath: fmt.Sprintf("/open-apis/drive/v1/medias/%s/download", validate.EncodePathSegment(item.FileToken)), QueryParams: query, }) if err != nil { return nil, output.ErrNetwork("download failed: %v", err) } defer resp.Body.Close() - - if !overwrite { - if _, statErr := runtime.FileIO().Stat(targetPath); statErr == nil { - return nil, output.ErrValidation("output file already exists: %s (use --overwrite to replace)", targetPath) - } - } result, err := runtime.FileIO().Save(targetPath, fileio.SaveOptions{🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/base/record_upload_attachment.go` around lines 770 - 784, The post-request file-existence check should be moved before initiating the download to avoid wasted API calls: before calling runtime.DoAPIStream(...) (the current ApiReq/DoAPIStream block), perform the same !overwrite check using runtime.FileIO().Stat(targetPath) and return output.ErrValidation(...) if the file exists; only after that check succeeds should you call runtime.DoAPIStream to create resp and defer resp.Body.Close(); keep the same error types and messages and preserve the defensive TOCTOU protection by checking immediately prior to the network call.
601-675: 💤 Low valueSame
file_tokenacross multiple attachment fields silently drops all but the first occurrence.In Base it is valid for the same
file_tokento appear in more than one attachment field of the same record. The dedup at Lines 631–633 keeps only the first occurrence (in sortedcurrentFieldIDorder), so theFieldIDreported back to the user in the download summary is whichever field sorted first — not necessarily the one the user expected.For the "download by
--file-token" path this only affects the reportedfield_idin the JSON envelope (the bytes are the same), so it's not a correctness bug, but users tracking attachments by(record, field)pairs may be confused. Worth a docstring onselectAttachmentDownloadItemsor a tip in the shortcut help noting that cross-field duplicates resolve to a single download keyed by the sorted-first field.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/base/record_upload_attachment.go` around lines 601 - 675, selectAttachmentDownloadItems currently deduplicates byToken which silently drops duplicate file_token occurrences across different fields; update the function to preserve all occurrences by changing byToken from map[string]baseAttachmentDownloadItem to map[string][]baseAttachmentDownloadItem (or similar) so you append each baseAttachmentDownloadItem for a given fileToken instead of skipping when exists, then adjust the downstream assembly: when tokens is empty, flatten all slices into result; when tokens are provided, look up the slice for each token and append all items (error only if token key missing); keep sorting but include FieldID (or FileToken+FieldID) in the tie-breaker to produce deterministic ordering; update references to byToken, the lookup branch that returned a single item, and any sorting comparator in selectAttachmentDownloadItems accordingly.
233-245: 💤 Low valuePossible nil deref guarded only by short-circuit — make the intent explicit.
info.IsDir()at Line 240 is only safe becausestatErr != nilshort-circuits the||. This works in practice (well-behavedStatimplementations don't return(nil, nil)), but readers must mentally trace the short-circuit to confirm safety, and a future change that reorders the operands would dereference a nil interface.Consider making the nil-safety explicit:
- info, statErr := runtime.FileIO().Stat(runtime.Str("output")) - if statErr != nil || !info.IsDir() { + info, statErr := runtime.FileIO().Stat(runtime.Str("output")) + if statErr != nil || info == nil || !info.IsDir() { return common.FlagErrorf("--output must be an existing directory when downloading multiple attachments or when --file-token is omitted") }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/base/record_upload_attachment.go` around lines 233 - 245, In validateRecordDownloadAttachment, make the nil-check for the FileInfo returned by runtime.FileIO().Stat explicit before calling info.IsDir(): call info, statErr := runtime.FileIO().Stat(runtime.Str("output")), then test if statErr != nil || info == nil || !info.IsDir() to decide returning common.FlagErrorf("--output must be an existing directory when downloading multiple attachments or when --file-token is omitted"); this ensures info is not nil before invoking info.IsDir() and preserves the original behavior.
270-294: 🏗️ Heavy liftPartial-failure on multi-file upload leaks already-uploaded media.
The loop uploads each file to Base media first, then a single
append_attachmentscall links them all at the end. If file N fails to upload, files 1…N-1 are already on the server but never linked to any record — they become orphaned media. Retrying will re-upload them, creating duplicate orphans on every attempt.This is a real concern for the multi-file workflow this PR is enabling. Consider one of:
- Calling
append_attachmentsincrementally per successful upload (more API calls, but no orphans).- Surfacing the orphaned
file_tokens in the error detail so users (or follow-up tooling) can clean up or re-link them.At minimum, the error returned at Line 283 should include the
file_tokens of the already-uploaded files so the user isn't blind.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/base/record_upload_attachment.go` around lines 270 - 294, The loop that uploads files (see appendItems and uploadAttachmentToBase) can leave already-uploaded media orphaned if a later upload fails; modify the error path inside the loop so that when uploadAttachmentToBase returns an error you collect and return the file_token(s) from the attachments already in appendItems (or from the returned attachment objects) in the error message/return value (e.g., extract file_token fields from attachmentAppendItem/attachment objects and include them in the error), or alternatively switch to calling append_attachments incrementally after each successful upload (i.e., call buildSingleCellAttachmentsBody + baseV3Call per uploaded file) to avoid orphans entirely; update the error returned where uploadAttachmentToBase fails to include the list of uploaded file_token strings so callers can clean up.
395-419: 💤 Low valueDrop
normalizeOptionalAttachmentFileTokensas it has no callers.
normalizeOptionalAttachmentFileTokens(lines 395–400) is unused in the codebase and delegates tonormalizeAttachmentFileTokens, which errors on duplicates. Meanwhile,normalizeOptionalDownloadAttachmentFileTokens(lines 402–419) silently dedupes viadedupeStringsPreserveOrder. The divergent duplicate semantics and unused helper create unnecessary maintenance burden.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/base/record_upload_attachment.go` around lines 395 - 419, Remove the dead wrapper function normalizeOptionalAttachmentFileTokens: delete the normalizeOptionalAttachmentFileTokens definition since it has no callers and only delegates to normalizeAttachmentFileTokens; before removing, search the repo for any references to normalizeOptionalAttachmentFileTokens and update them to call normalizeAttachmentFileTokens or normalizeOptionalDownloadAttachmentFileTokens as appropriate, and ensure duplicate-handling semantics remain intentional between normalizeAttachmentFileTokens (which errors on duplicates) and normalizeOptionalDownloadAttachmentFileTokens (which dedupes) so behavior is not accidentally changed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@shortcuts/base/record_upload_attachment.go`:
- Around line 506-511: detectAttachmentImageDimensions fails to decode WebP
because only PNG/JPEG/GIF decoders are registered, causing the misleading
warning in the branch that checks strings.HasPrefix(mimeType, "image/"); either
register the WebP decoder by adding the blank import _ "golang.org/x/image/webp"
alongside the existing blank imports (lines where image decoders are registered)
so detectAttachmentImageDimensions can return real dimensions, or change the
warning condition in the code that assigns
attachment["image_width"]/["image_height"] (the if/else block using
detectAttachmentImageDimensions) to only print the warning for the specific mime
types you actually decode (e.g., "image/png","image/jpeg","image/gif") instead
of all image/* MIME types.
---
Nitpick comments:
In `@shortcuts/base/record_upload_attachment.go`:
- Around line 770-784: The post-request file-existence check should be moved
before initiating the download to avoid wasted API calls: before calling
runtime.DoAPIStream(...) (the current ApiReq/DoAPIStream block), perform the
same !overwrite check using runtime.FileIO().Stat(targetPath) and return
output.ErrValidation(...) if the file exists; only after that check succeeds
should you call runtime.DoAPIStream to create resp and defer resp.Body.Close();
keep the same error types and messages and preserve the defensive TOCTOU
protection by checking immediately prior to the network call.
- Around line 601-675: selectAttachmentDownloadItems currently deduplicates
byToken which silently drops duplicate file_token occurrences across different
fields; update the function to preserve all occurrences by changing byToken from
map[string]baseAttachmentDownloadItem to map[string][]baseAttachmentDownloadItem
(or similar) so you append each baseAttachmentDownloadItem for a given fileToken
instead of skipping when exists, then adjust the downstream assembly: when
tokens is empty, flatten all slices into result; when tokens are provided, look
up the slice for each token and append all items (error only if token key
missing); keep sorting but include FieldID (or FileToken+FieldID) in the
tie-breaker to produce deterministic ordering; update references to byToken, the
lookup branch that returned a single item, and any sorting comparator in
selectAttachmentDownloadItems accordingly.
- Around line 233-245: In validateRecordDownloadAttachment, make the nil-check
for the FileInfo returned by runtime.FileIO().Stat explicit before calling
info.IsDir(): call info, statErr :=
runtime.FileIO().Stat(runtime.Str("output")), then test if statErr != nil ||
info == nil || !info.IsDir() to decide returning common.FlagErrorf("--output
must be an existing directory when downloading multiple attachments or when
--file-token is omitted"); this ensures info is not nil before invoking
info.IsDir() and preserves the original behavior.
- Around line 270-294: The loop that uploads files (see appendItems and
uploadAttachmentToBase) can leave already-uploaded media orphaned if a later
upload fails; modify the error path inside the loop so that when
uploadAttachmentToBase returns an error you collect and return the file_token(s)
from the attachments already in appendItems (or from the returned attachment
objects) in the error message/return value (e.g., extract file_token fields from
attachmentAppendItem/attachment objects and include them in the error), or
alternatively switch to calling append_attachments incrementally after each
successful upload (i.e., call buildSingleCellAttachmentsBody + baseV3Call per
uploaded file) to avoid orphans entirely; update the error returned where
uploadAttachmentToBase fails to include the list of uploaded file_token strings
so callers can clean up.
- Around line 395-419: Remove the dead wrapper function
normalizeOptionalAttachmentFileTokens: delete the
normalizeOptionalAttachmentFileTokens definition since it has no callers and
only delegates to normalizeAttachmentFileTokens; before removing, search the
repo for any references to normalizeOptionalAttachmentFileTokens and update them
to call normalizeAttachmentFileTokens or
normalizeOptionalDownloadAttachmentFileTokens as appropriate, and ensure
duplicate-handling semantics remain intentional between
normalizeAttachmentFileTokens (which errors on duplicates) and
normalizeOptionalDownloadAttachmentFileTokens (which dedupes) so behavior is not
accidentally changed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 80f44a1e-3279-4e26-b754-5276485d097a
📒 Files selected for processing (2)
shortcuts/base/base_execute_test.goshortcuts/base/record_upload_attachment.go
🚧 Files skipped from review as they are similar to previous changes (1)
- shortcuts/base/base_execute_test.go
* feat: support base attachment APIs * fix: handle duplicate base attachment downloads * fix: remove unused attachment token helper
Summary
Refactor Base attachment handling onto the new attachment APIs and add first-class commands for downloading and removing Base record attachments. This improves agent ergonomics for multi-attachment workflows and avoids manual Base attachment download handling.
Changes
base +record-upload-attachmentto upload one or more local files, send image width/height for PNG/JPEG/GIF uploads, and append uploaded file tokens throughappend_attachments.base +record-download-attachmentfor Base attachment downloads byrecord-id, optionally filtered by one or more--file-tokenvalues; omitting--file-tokendownloads all attachments in the record.base +record-remove-attachmentfor removing one or more attachment file tokens from a record attachment cell.Breaking Changes
base +record-upload-attachment --nameis no longer supported; uploaded attachment names come from local file basenames.base +record-upload-attachmentnow prints theappend_attachmentsserverdatadirectly instead of older CLI-composed fields such asupdated,uploaded, orattachment.append_attachmentsinstead of reading the existing record and patching the whole attachment array.Release Note
--namesupport has been removed.Test Plan
make unit-testgo vet ./...gofmt -l .go mod tidyleftgo.mod/go.sumunchangedgo test ./tests/cli_e2e/base -run TestBase_AttachmentDryRun -count=1go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 run --new-from-rev=upstream/mainNote:
go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 run --new-from-rev=origin/maincurrently reports pre-existing unrelated depguard/forbidigo issues inshortcuts/event,shortcuts/mail,shortcuts/sheets,shortcuts/minutes, andinternal/keychain; the same lint againstupstream/mainreports 0 issues for this PR diff.Related Issues
Summary by CodeRabbit
New Features
--nameflag deprecated/rejected; image dimension autodetection.Documentation
Tests