Skip to content

fix(im): harden resource downloads with validated ranged streams - #2223

Merged
liangshuo-1 merged 13 commits into
mainfrom
fix/im-resilient-download
Aug 7, 2026
Merged

fix(im): harden resource downloads with validated ranged streams#2223
liangshuo-1 merged 13 commits into
mainfrom
fix/im-resilient-download

Conversation

@liangshuo-1

@liangshuo-1 liangshuo-1 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Reworks the IM resource-download path explored in #2176 into a reusable internal streaming boundary while keeping this PR scoped to IM.

  • validates every full or ranged response before publishing bytes
  • resumes interrupted range bodies from the last delivered offset
  • keeps retryability transport-owned and opt-in through WithReplaySafe
  • replaces the previous 120-second absolute request deadline with a 60-second progress-based idle timeout
  • preserves atomic FileIO publication and typed error metadata
  • deliberately leaves Drive and Minutes unchanged for separate PRs

Why

The existing IM range reader coupled OpenAPI construction, range progression, body framing, retries, and error mapping in one shortcut. That made truncated bodies, representation changes, malformed Content-Range, gateway pacing, and stalled connections difficult to handle consistently.

This change separates those invariants into:

OAPI transport → immutable source → validated sequential reader → exact-length reader → FileIO sink

User impact

Large IM file downloads now:

  • use exact, non-overlapping 32 MiB ranges
  • retry transient request and ranged-body failures within a bounded wait budget
  • terminate a request after 60 seconds without response headers or body progress
  • allow healthy slow transfers to continue as long as bytes keep arriving
  • honor Retry-After and Lark gateway reset headers for replay-safe requests
  • reject wrong offsets, contradictory lengths, changed representations, and unexpected content encoding
  • avoid leaving partially published local files

The idle timeout resets on every successful body read and does not run while the caller is between reads. Images continue to use one full response. CLI flags and output shape are unchanged.

Scope

This PR only migrates im +messages-resources-download. Drive and Minutes integrations are intentionally excluded and will be evaluated independently.

Validation

  • make unit-test
  • go vet ./...
  • go mod tidy -diff
  • golangci-lint run --new-from-patch <PR diff> — 0 issues
  • race-tested header stalls, body stalls with offset resumption, slow progress, consumer pauses, caller cancellation/deadline precedence, and close-time interruption
  • added an IM workflow test that uploads a file, reads back its file_key, downloads it, and verifies the bytes

Summary by CodeRabbit

  • New Features

    • Added reliable large-file downloads with ranged streaming, automatic resumption, integrity validation, and multipart continuation.
    • Added retries for eligible transient download failures with server-provided delays, bounded backoff, and idle-timeout handling.
    • Added support for authenticated API and direct URL download sources.
    • Added retry-delay details and classifications for protocol and representation changes in typed network errors.
    • Improved streaming request handling for rate limits, timeouts, and temporary server failures.
  • Documentation

    • Simplified Agent-facing download guidance to the user-visible command contract.

Framework contract

The reusable core now supports both authenticated OAPI and caller-validated pre-signed URL transports. ImmutableSource may combine exact ranges without a validator; MutableSource requires a strong ETag before combining responses and otherwise falls back before publishing bytes. URL transport preserves caller-owned redirect/SSRF policy, removes absolute http.Client.Timeout in favor of the progress-based idle timeout, and consumes only standard Retry-After pacing.

This PR migrates only IM behavior. Drive and Minutes remain unchanged and can adopt these framework contracts in separate PRs.

@github-actions github-actions Bot added domain/im PR touches the im domain size/XL Architecture-level or global-impact change labels Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds typed retry metadata, replay-safe client error classification, a validated ranged-download engine with idle timeouts, and IM resource-download integration. Tests and documentation cover retries, continuation, representation changes, and CLI workflows.

Changes

Download reliability and retry handling

Layer / File(s) Summary
Typed retry contracts and client classification
errs/..., internal/client/..., internal/ratelimit/..., internal/recovery/render_test.go
Errors expose retry delays and new network subtypes. Streaming requests classify retryable transport, timeout, rate-limit, and HTTP failures.
Validated ranged-download engine
internal/download/download.go, internal/download/exact_length.go, internal/download/response.go, internal/download/source.go, internal/download/*_test.go
The engine supports range probing, multipart streaming, retries, response validation, exact-length checks, strong ETags, If-Range, continuation, and bounded retry waits.
Idle-timeout recovery
internal/download/idle_timeout.go, internal/download/idle_timeout_test.go
Requests and body reads use progress-based idle timeouts. Caller cancellation and deadlines remain terminal.
Download transports
internal/download/transport.go, internal/download/transport_test.go, internal/ratelimit/headers.go
OAPI and direct-URL transports create independent requests, forward range headers, classify responses, and parse retry metadata.
IM resource-download integration
shortcuts/im/..., skills/lark-im/references/..., tests/cli_e2e/im/...
IM downloads use the shared stream and immutable source. Tests cover interrupted transfers, continuation, dry runs, content parsing, and an end-to-end workflow.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: bug

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant IMDownload
  participant OAPI
  participant DownloadStream
  participant ResourceAPI
  CLI->>IMDownload: request resource download
  IMDownload->>OAPI: create immutable resource source
  OAPI->>ResourceAPI: send replay-safe range request
  ResourceAPI-->>DownloadStream: return response
  DownloadStream->>OAPI: request validated continuation range
  OAPI->>ResourceAPI: fetch next range with If-Range
  DownloadStream-->>IMDownload: provide validated bytes
  IMDownload-->>CLI: save file and report metadata
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.49% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely summarizes the main change: hardened IM resource downloads with validated ranged streams.
Description check ✅ Passed The description explains the motivation, scope, implementation changes, user impact, validation, and related issue context in sufficient detail.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/im-resilient-download

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@liangshuo-1
liangshuo-1 marked this pull request as ready for review August 6, 2026 15:21
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@d5cd7beacc68b8f273015e3fb7abb8c9d1bd08f5

🧩 Skill update

npx skills add larksuite/cli#fix/im-resilient-download -y -g

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (8)
errs/subtypes.go (1)

58-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider naming the new constant SubtypeNetworkRepresentationChanged.

All sibling network subtypes use the SubtypeNetwork* prefix. SubtypeRepresentationChanged is produced by errs.NewNetworkError, so the prefix would keep the constant discoverable next to its peers. The wire value representation_changed does not need to change.

🤖 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 `@errs/subtypes.go` around lines 58 - 61, Rename the constant
SubtypeRepresentationChanged to SubtypeNetworkRepresentationChanged to match the
naming convention of sibling network subtypes and its use with
errs.NewNetworkError. Keep the wire value "representation_changed" unchanged and
update all references to the renamed constant.
internal/download/download.go (1)

361-451: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Recommend extracting the EOF and error retry bookkeeping.

Read carries five concerns in one function: chunk framing checks, EOF handling, error classification, retry bookkeeping, and next-range opening. The two retry branches at Lines 380-394 and Lines 408-424 repeat the same four assignments (retryErr, partRetries++, retryPending, chunkRead/chunkWant reset). A small helper such as beginPartRetry(failure error) would remove the duplication and make the state machine easier to audit.

This is a readability improvement only. The current control flow appears correct.

🤖 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 `@internal/download/download.go` around lines 361 - 451, Extract the duplicated
retry-state updates from sequentialPartReader.Read into a helper such as
beginPartRetry(failure error). Use it in both the short-read EOF retry branch
and the retryable read-error branch, preserving assignment of retryErr,
incrementing partRetries, setting retryPending, and resetting chunkRead and
chunkWant without changing control flow.
internal/download/exact_length.go (1)

46-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider errors.Is(err, io.EOF) for the EOF comparisons.

Lines 46 and 63 compare with ==. The current sources return a bare io.EOF, so the behavior is correct today. A wrapped EOF from a future source implementation would be classified as a transport error instead of a clean end of stream. errors.Is removes that coupling.

Also applies to: 63-65

🤖 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 `@internal/download/exact_length.go` around lines 46 - 49, Update the EOF
checks in the exact-length download flow to use errors.Is for both comparisons
around the affected branches, including the logic near lines 46 and 63. Preserve
the existing clean end-of-stream handling while recognizing wrapped io.EOF
values; leave non-EOF transport errors unchanged.
shortcuts/im/im_messages_resources_download.go (1)

144-157: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the local variable; it shadows the download package.

Line 145 declares a local variable named download. The file imports the package github.com/larksuite/cli/internal/download under the same name at line 15. Inside this function the package identifier is no longer reachable. The code compiles today because the function body makes no further package reference, but any later use of download.Options or download.Open in this scope will fail to compile. Rename the variable to stream.

♻️ Proposed rename
-	download, err := openIMResourceDownload(ctx, runtime, messageID, fileKey, fileType)
+	stream, err := openIMResourceDownload(ctx, runtime, messageID, fileKey, fileType)
 	if err != nil {
 		return "", 0, err
 	}
-	defer download.Body.Close()
+	defer stream.Body.Close()
 
-	finalPath := resolveIMResourceDownloadPath(outputPath, download.Header.Get("Content-Type"), download.Header.Get("Content-Disposition"), preserveBasename)
-	sizeBytes := download.ContentLength
+	finalPath := resolveIMResourceDownloadPath(outputPath, stream.Header.Get("Content-Type"), stream.Header.Get("Content-Disposition"), preserveBasename)
+	sizeBytes := stream.ContentLength
 
 	result, err := runtime.FileIO().Save(finalPath, fileio.SaveOptions{
-		ContentType:   download.Header.Get("Content-Type"),
+		ContentType:   stream.Header.Get("Content-Type"),
 		ContentLength: sizeBytes,
-	}, download.Body)
+	}, stream.Body)
🤖 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/im/im_messages_resources_download.go` around lines 144 - 157,
Rename the local variable returned by openIMResourceDownload in
downloadIMResourceToPath from download to stream, and update all references to
its Body, Header, and ContentLength fields. Preserve the imported download
package name for future use.
internal/download/download_test.go (2)

338-347: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the redundant branch.

The requests == 1 branch returns the same response as the fallback branch. Collapse both into one return.

♻️ Proposed simplification
-	requests := 0
 	stream, err := openTest(context.Background(), func(_ context.Context, req Request) (*http.Response, error) {
-		requests++
-		if requests == 1 {
-			return testPartial(payload[:4], 0, 3, int64(len(payload)), `"v1"`), nil
-		}
 		return testPartial(payload[:4], 0, 3, int64(len(payload)), `"v1"`), nil
 	}, testOptions())
🤖 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 `@internal/download/download_test.go` around lines 338 - 347, Remove the
redundant requests == 1 conditional in TestOpenRejectsResponseAtWrongOffset and
return the shared testPartial response directly from the request callback,
preserving the existing requests counter only if it is needed by the test.

529-549: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add the short-body case or drop the table.

The table has one row named long. The test name says "contradiction", which covers both a longer and a shorter body than Content-Range promises. Add a short row that delivers fewer bytes and asserts the expected framing error, or replace the table with a single direct case.

🤖 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 `@internal/download/download_test.go` around lines 529 - 549, Extend
TestOpenRejectsBodyLengthContradiction with a short-body case that supplies
fewer bytes than the promised Content-Range length and verifies the same framing
error; otherwise remove the table and convert the existing long case into a
direct test.
skills/lark-im/references/lark-im-messages-resources-download.md (1)

47-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Note that chunking applies to --type file only.

openIMResourceDownload in shortcuts/im/im_messages_resources_download.go line 177 sets DisableMultipart: fileType != "file", so --type image always uses one full response. Add that condition to this table so the behavior is discoverable.

📝 Proposed addition
 | Behavior | Details |
 |----------|---------|
+| Applies to | `--type file` only; `--type image` uses a single full response |
 | First part | Up to 8 MiB to discover the total representation size |
🤖 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 `@skills/lark-im/references/lark-im-messages-resources-download.md` around
lines 47 - 54, Update the download behavior table to state that chunking applies
only to --type file; image downloads use a single full response because
openIMResourceDownload sets DisableMultipart when fileType is not "file".
tests/cli_e2e/im/message_resource_download_workflow_test.go (1)

8-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the MD5 comparison to clear the SAST finding.

The static analysis gate flags crypto/md5 at line 79 as a broken hash (CWE-327). The intent here is a byte-for-byte fixture comparison, not a security check, so the finding is a false positive on intent. It will still fail or noise the gate on every run. Compare the lengths and then the bytes, which is stronger and needs no hash. require.Equal on the length first keeps the failure output short.

♻️ Proposed change
 import (
 	"bytes"
 	"context"
-	"crypto/md5"
 	"os"
 		got, readErr := os.ReadFile(filepath.Join(downloadDir, "downloaded.bin"))
 		require.NoError(t, readErr)
-		require.Equal(t, md5.Sum(payload), md5.Sum(got),
-			"downloaded bytes must match the uploaded fixture byte for byte")
+		require.Equal(t, len(payload), len(got),
+			"downloaded size must match the uploaded fixture")
+		require.True(t, bytes.Equal(payload, got),
+			"downloaded bytes must match the uploaded fixture byte for byte")
 	})

If you prefer a digest, use sha256.Sum256.

Also applies to: 79-80

🤖 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 `@tests/cli_e2e/im/message_resource_download_workflow_test.go` at line 8,
Remove the crypto/md5 dependency and update the fixture comparison in the
relevant download workflow test to compare expected and actual byte lengths
first, then compare the byte contents with require.Equal. Preserve the existing
byte-for-byte validation while eliminating the MD5-based comparison.

Source: Linters/SAST tools

🤖 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 `@errs/predicates_test.go`:
- Around line 15-35: Extend TestRetryAfter with cases covering a negative
RetryAfterSeconds value and a value that saturates at
time.Duration(math.MaxInt64). Assert both the returned duration and success flag
directly, matching the contract for each boundary case.

In `@internal/client/client_test.go`:
- Around line 536-559: Update the error-path tests in
internal/client/client_test.go at lines 536-559, 561-580, and 708-727 to assert
the complete typed transport-error contract: use errs.ProblemOf to verify
CategoryNetwork, and SubtypeNetworkTimeout where applicable; use errors.Is to
verify context.DeadlineExceeded at lines 561-580; and retain the TLS error in a
variable at lines 708-727 so errors.Is verifies the original cause. Apply the
relevant assertions at each site, including the existing retryability checks.

In `@tests/cli_e2e/im/messages_resources_download_dryrun_test.go`:
- Around line 93-98: Extend the validation-reject assertions in the test subcase
around the existing exit-code checks to verify the offending flag via stderr’s
error.param and confirm result.Stdout is empty. Follow the test’s typed
validation contract by asserting error metadata from result.Stderr, preserving
the existing error.type and error.subtype checks and pinning error.param to the
emitted value.

---

Nitpick comments:
In `@errs/subtypes.go`:
- Around line 58-61: Rename the constant SubtypeRepresentationChanged to
SubtypeNetworkRepresentationChanged to match the naming convention of sibling
network subtypes and its use with errs.NewNetworkError. Keep the wire value
"representation_changed" unchanged and update all references to the renamed
constant.

In `@internal/download/download_test.go`:
- Around line 338-347: Remove the redundant requests == 1 conditional in
TestOpenRejectsResponseAtWrongOffset and return the shared testPartial response
directly from the request callback, preserving the existing requests counter
only if it is needed by the test.
- Around line 529-549: Extend TestOpenRejectsBodyLengthContradiction with a
short-body case that supplies fewer bytes than the promised Content-Range length
and verifies the same framing error; otherwise remove the table and convert the
existing long case into a direct test.

In `@internal/download/download.go`:
- Around line 361-451: Extract the duplicated retry-state updates from
sequentialPartReader.Read into a helper such as beginPartRetry(failure error).
Use it in both the short-read EOF retry branch and the retryable read-error
branch, preserving assignment of retryErr, incrementing partRetries, setting
retryPending, and resetting chunkRead and chunkWant without changing control
flow.

In `@internal/download/exact_length.go`:
- Around line 46-49: Update the EOF checks in the exact-length download flow to
use errors.Is for both comparisons around the affected branches, including the
logic near lines 46 and 63. Preserve the existing clean end-of-stream handling
while recognizing wrapped io.EOF values; leave non-EOF transport errors
unchanged.

In `@shortcuts/im/im_messages_resources_download.go`:
- Around line 144-157: Rename the local variable returned by
openIMResourceDownload in downloadIMResourceToPath from download to stream, and
update all references to its Body, Header, and ContentLength fields. Preserve
the imported download package name for future use.

In `@skills/lark-im/references/lark-im-messages-resources-download.md`:
- Around line 47-54: Update the download behavior table to state that chunking
applies only to --type file; image downloads use a single full response because
openIMResourceDownload sets DisableMultipart when fileType is not "file".

In `@tests/cli_e2e/im/message_resource_download_workflow_test.go`:
- Line 8: Remove the crypto/md5 dependency and update the fixture comparison in
the relevant download workflow test to compare expected and actual byte lengths
first, then compare the byte contents with require.Equal. Preserve the existing
byte-for-byte validation while eliminating the MD5-based comparison.
🪄 Autofix

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 Plus

Run ID: 66673536-aa82-44e8-880b-87c4677e80c0

📥 Commits

Reviewing files that changed from the base of the PR and between f7d0326 and 52de8b7.

📒 Files selected for processing (31)
  • errs/ERROR_CONTRACT.md
  • errs/marshal_test.go
  • errs/predicates.go
  • errs/predicates_test.go
  • errs/subtypes.go
  • errs/types.go
  • internal/client/api_errors.go
  • internal/client/client.go
  • internal/client/client_test.go
  • internal/client/option.go
  • internal/download/download.go
  • internal/download/download_test.go
  • internal/download/exact_length.go
  • internal/download/exact_length_test.go
  • internal/download/response.go
  • internal/download/response_test.go
  • internal/download/source.go
  • internal/download/transport.go
  • internal/download/transport_test.go
  • internal/ratelimit/headers.go
  • internal/ratelimit/headers_test.go
  • internal/recovery/render_test.go
  • shortcuts/im/helpers_network_test.go
  • shortcuts/im/helpers_test.go
  • shortcuts/im/im_errors.go
  • shortcuts/im/im_messages_resources_download.go
  • skills/lark-im/references/lark-im-messages-resources-download.md
  • tests/cli_e2e/im/coverage.md
  • tests/cli_e2e/im/message_resource_download_content_test.go
  • tests/cli_e2e/im/message_resource_download_workflow_test.go
  • tests/cli_e2e/im/messages_resources_download_dryrun_test.go
💤 Files with no reviewable changes (2)
  • shortcuts/im/helpers_test.go
  • shortcuts/im/im_errors.go

Comment thread errs/predicates_test.go
Comment thread internal/client/client_test.go
Comment thread tests/cli_e2e/im/messages_resources_download_dryrun_test.go
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.33333% with 88 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.46%. Comparing base (f7d0326) to head (d5cd7be).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
internal/download/download.go 90.12% 23 Missing and 18 partials ⚠️
internal/download/idle_timeout.go 79.71% 10 Missing and 4 partials ⚠️
internal/download/transport.go 90.69% 4 Missing and 4 partials ⚠️
internal/download/exact_length.go 83.33% 3 Missing and 3 partials ⚠️
internal/download/source.go 88.88% 3 Missing and 2 partials ⚠️
errs/predicates.go 77.77% 2 Missing and 2 partials ⚠️
internal/client/api_errors.go 77.77% 3 Missing and 1 partial ⚠️
internal/ratelimit/headers.go 89.74% 2 Missing and 2 partials ⚠️
internal/client/client.go 92.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2223      +/-   ##
==========================================
+ Coverage   76.22%   76.46%   +0.24%     
==========================================
  Files         987      998      +11     
  Lines      104280   106689    +2409     
==========================================
+ Hits        79486    81585    +2099     
- Misses      18751    18947     +196     
- Partials     6043     6157     +114     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 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 `@internal/download/transport_test.go`:
- Around line 99-106: Strengthen the error-path assertions in
internal/download/transport_test.go:99-106 by checking problem.Category equals
errs.CategoryNetwork; at 128-131, assert the same category and verify
errors.Is(err, tt.err); at 145-168, assert CategoryNetwork, retain the redirect
error in a variable, and verify errors.Is(err, redirectErr). Do not assert Param
for these network errors.
- Line 181: Update both assertions in the transport test around the QueryIf
checks to verify that the “empty” query key is absent, not merely that its
retrieved value is empty. Use larkcore.QueryParams’ presence-check API if
available; otherwise inspect the query-parameter map directly, while preserving
the existing path, type, and version assertions.

In `@internal/download/transport.go`:
- Around line 168-170: Update the retry metadata handling in
internal/download/transport.go:168-170 to use the gateway-aware rate-limit
parser, preserving Retry-After precedence while falling back to
X-Ogw-Ratelimit-Reset. In internal/download/transport_test.go:84-107, add a
retryable response containing only X-Ogw-Ratelimit-Reset and assert the
resulting errs.RetryAfter value.
🪄 Autofix

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 Plus

Run ID: 9767de5e-c0b4-438f-b974-b680d3f8f96c

📥 Commits

Reviewing files that changed from the base of the PR and between c0f49a4 and 878c3db.

📒 Files selected for processing (9)
  • internal/client/api_errors.go
  • internal/download/download.go
  • internal/download/download_test.go
  • internal/download/response_test.go
  • internal/download/source.go
  • internal/download/transport.go
  • internal/download/transport_test.go
  • internal/ratelimit/headers.go
  • internal/ratelimit/headers_test.go
💤 Files with no reviewable changes (1)
  • internal/download/response_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/ratelimit/headers.go
  • internal/ratelimit/headers_test.go
  • internal/download/download_test.go

Comment thread internal/download/transport_test.go
Comment thread internal/download/transport_test.go
Comment thread internal/download/transport.go
@liangshuo-1
liangshuo-1 merged commit 0d6f2c6 into main Aug 7, 2026
@liangshuo-1
liangshuo-1 deleted the fix/im-resilient-download branch August 7, 2026 09:45
@liangshuo-1 liangshuo-1 mentioned this pull request Aug 7, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/im PR touches the im domain size/XL Architecture-level or global-impact change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants