Skip to content

fix(shortcuts): classify media stream recovery errors - #2299

Open
wittam-01 wants to merge 1 commit into
larksuite:mainfrom
wittam-01:fix/media-stream-recovery-hints
Open

fix(shortcuts): classify media stream recovery errors#2299
wittam-01 wants to merge 1 commit into
larksuite:mainfrom
wittam-01:fix/media-stream-recovery-hints

Conversation

@wittam-01

@wittam-01 wittam-01 commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Classify scope and rate-limit failures returned by streaming media requests in Docs and Drive shortcuts, while keeping the change inside the owning domain packages.

Changes

  • Recover Lark business codes 99991672 and 99991400 from the current streaming network-error message shape and reuse the existing typed API classifier.
  • Preserve the original cause and log ID, add stop-retrying scope guidance, and use generic exponential-backoff guidance for rate limits.
  • Add regression coverage for drive +download, docs +media-download, drive +preview, and docs +media-preview.

Test Plan

  • Targeted shortcuts/doc tests pass.
  • Targeted shortcuts/drive tests pass.
  • make build passes.
  • Live bot validation for docs +media-preview triggered 99991400 and returned the expected typed rate-limit response; retries stopped after the bounded batch.
  • Full unit-test and make test suites were not run.

Related Issues

  • None

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling for document media and Drive downloads and previews.
    • Permission and rate-limit failures now provide clearer recovery guidance.
    • App-scope authorization errors include guidance to stop retrying until permissions are updated.
    • Rate-limit errors consistently indicate that retrying with backoff may help.
    • Underlying network and API error details are preserved for better troubleshooting.

@github-actions github-actions Bot added domain/ccm PR touches the ccm domain size/M Single-domain feat or fix with limited business impact labels Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Doc and Drive media streams now classify transport-formatted permission and rate-limit errors. The flows preserve original causes and metadata, add shared recovery hints, and expand tests for app-scope and retryable failures.

Changes

Media error recovery

Layer / File(s) Summary
Doc media classification and recovery
shortcuts/doc/doc_errors.go, shortcuts/doc/doc_media_download.go, shortcuts/doc/doc_media_preview.go, shortcuts/doc/doc_media_test.go
Doc media stream errors are classified into typed permission or rate-limit errors. Shared recovery hints cover missing scopes and backoff guidance. Tests verify causes, metadata, retryability, and exact hints.
Drive file-read classification and recovery
shortcuts/drive/drive_errors.go, shortcuts/drive/drive_download.go, shortcuts/drive/drive_io_test.go
Drive download stream errors are classified before wrapping. Download recovery adds missing-scope and rate-limit guidance while preserving HTTP causes and log IDs.
Drive preview recovery integration
shortcuts/drive/drive_preview_common.go, shortcuts/drive/drive_preview_test.go
Preview candidate and source-file errors receive recovery hints. Source-file stream tests cover permission and rate-limit classifications, causes, scopes, and retryability.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DriveDownload
  participant classifyDriveFileReadStreamError
  participant DriveFileReadRecovery
  DriveDownload->>classifyDriveFileReadStreamError: final stream error
  classifyDriveFileReadStreamError->>DriveFileReadRecovery: typed API error and original cause
  DriveFileReadRecovery-->>DriveDownload: classified error with recovery hint
Loading

Possibly related PRs

Suggested reviewers: evandance

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: classifying media stream recovery errors in the shortcuts package.
Description check ✅ Passed The description includes all required sections and clearly documents the changes, testing, and the fact that full suites were not run.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

🧹 Nitpick comments (7)
shortcuts/doc/doc_media_test.go (2)

743-748: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant "1 minute" assertions remain after the switch to exact hint equality. Each site now compares the hint to a golden constant and then also checks that the hint does not contain "1 minute". A string equal to the constant cannot contain that substring, so the second check can never fail. The shared root cause is the migration from substring assertions to exact-equality assertions without removing the superseded checks.

  • shortcuts/doc/doc_media_test.go#L743-L748: remove the strings.Contains(problem.Hint, "1 minute") check that follows the equality assertion.
  • shortcuts/drive/drive_io_test.go#L1832-L1837: remove the same trailing check.
  • shortcuts/drive/drive_io_test.go#L1867-L1872: remove the same trailing check.
🤖 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/doc/doc_media_test.go` around lines 743 - 748, Remove the redundant
strings.Contains(problem.Hint, "1 minute") assertions following exact hint
equality checks in shortcuts/doc/doc_media_test.go lines 743-748,
shortcuts/drive/drive_io_test.go lines 1832-1837, and
shortcuts/drive/drive_io_test.go lines 1867-1872; retain each golden constant
comparison.

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

Add log-ID coverage to match the Drive test.

classifyDocMediaStreamError copies problem.LogID into the reconstructed response header at doc_errors.go lines 58-60. This stub sets no log-ID header, so that branch stays untested for Doc. The Drive equivalent in shortcuts/drive/drive_io_test.go sets larkcore.HttpHeaderKeyLogId and asserts problem.LogID. Mirror it here.

💚 Proposed additions
 					},
+					Headers: http.Header{
+						"Content-Type":              []string{"application/json"},
+						larkcore.HttpHeaderKeyLogId: []string{"log-doc-stream"},
+					},
 				})
 				problem, ok := errs.ProblemOf(err)
-				if !ok || problem.Code != apiFailure.code {
-					t.Fatalf("problem=%+v ok=%v, want code=%d", problem, ok, apiFailure.code)
+				if !ok || problem.Code != apiFailure.code || problem.LogID != "log-doc-stream" {
+					t.Fatalf("problem=%+v ok=%v, want code=%d and stream log id", problem, ok, apiFailure.code)
 				}

This requires the larkcore import in the test file.

🤖 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/doc/doc_media_test.go` around lines 879 - 903, Add log-ID coverage
to the Doc media error test around the HTTP stub and problem assertions: import
the existing larkcore package, set larkcore.HttpHeaderKeyLogId on the stub
response using a test log ID, and assert the resulting problem.LogID matches it,
mirroring the Drive test while preserving the existing error checks.
shortcuts/drive/drive_errors.go (2)

123-136: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the shared hint block.

withDrivePreviewRecoveryHint repeats the app-scope and rate-limit logic from withDriveDownloadRecoveryHint lines 109-120. A single helper keeps the two paths aligned and removes the repeated literal guard fragments.

🤖 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/drive/drive_errors.go` around lines 123 - 136, Extract the shared
app-scope and rate-limit hint logic from withDrivePreviewRecoveryHint and
withDriveDownloadRecoveryHint into one helper, then have both recovery functions
reuse it. Preserve the existing guards, hint constants, and return behavior
while removing the duplicated literal checks.

116-119: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Do not discard the ok flag before dereferencing problem.

Line 116 ignores the second return of errs.ProblemOf and then reads problem.Hint. This is safe today only because driveFileReadIsRateLimit returns false when ProblemOf fails. The guard lives in a different function, so the safety is implicit. If the predicate ever changes, this line panics. The same pattern exists at line 131 in withDrivePreviewRecoveryHint.

🛡️ Proposed guard
-	problem, _ := errs.ProblemOf(err)
-	if strings.Contains(problem.Hint, "exponential backoff") {
+	problem, ok := errs.ProblemOf(err)
+	if !ok || problem == nil || strings.Contains(problem.Hint, "exponential backoff") {
 		return err
 	}
 	return appendDriveExportRecoveryHint(err, driveFileReadRateLimitHint)

Apply the same change at line 131.

🤖 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/drive/drive_errors.go` around lines 116 - 119, Check the success
flag returned by errs.ProblemOf in both the driveFileReadIsRateLimit path and
withDrivePreviewRecoveryHint before accessing problem.Hint. Only evaluate the
exponential-backoff hint when conversion succeeds; preserve the existing return
behavior otherwise.
shortcuts/doc/doc_errors.go (3)

102-124: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the shared hint block.

withDocMediaDownloadRecoveryHint and withDocMediaPreviewRecoveryHint repeat the same app-scope and rate-limit logic. The idempotency guards also repeat literal fragments of the hint constants ("stop retrying now", "exponential backoff"). A single helper removes the drift risk between the guard text and the constant text.

♻️ Proposed helper
+func appendDocMediaSharedRecoveryHints(problem *errs.Problem) {
+	if problem.Code == 99991672 && !strings.Contains(problem.Hint, docMediaAppScopeHint) {
+		appendDocRecoveryHint(problem, docMediaAppScopeHint)
+	}
+	if docMediaIsRateLimit(problem) && !strings.Contains(problem.Hint, docMediaRateLimitHint) {
+		appendDocRecoveryHint(problem, docMediaRateLimitHint)
+	}
+}

Then call it from both functions.

🤖 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/doc/doc_errors.go` around lines 102 - 124, Extract the duplicated
app-scope and rate-limit hint logic from withDocMediaDownloadRecoveryHint and
withDocMediaPreviewRecoveryHint into one shared helper. Have the helper perform
the existing problem validation, append both hints, and use the hint constants
or shared guard values instead of repeating literal fragments; then call it from
both recovery functions while preserving their current error return behavior.

72-81: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Attach the cause for every classified error type.

If classified is neither *errs.PermissionError nor *errs.APIError, the function returns it without a cause. The original transport error and its HTTP status then disappear from the error chain. Today ClassifyAPIResponse maps 99991672 and 99991400 to those two types, so this is a defensive gap only. Consider returning the original err when no cause can be attached.

♻️ Proposed fallback
 	var apiErr *errs.APIError
 	if errors.As(classified, &apiErr) {
 		apiErr.WithCause(err)
+		return classified
 	}
-	return classified
+	// No known typed carrier for the cause; keep the transport error intact.
+	return err
🤖 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/doc/doc_errors.go` around lines 72 - 81, Update the
error-classification flow around the PermissionError and APIError handling so
every classified error preserves the original err as its cause. If classified is
neither supported type, return or wrap the original err using the established
fallback rather than returning classified without a cause, while preserving the
existing typed-error behavior.

33-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

The media-read recovery contract is duplicated across the doc and drive packages. classifyDocMediaStreamError and classifyDriveFileReadStreamError have identical bodies, and the four recovery-hint constants carry identical text. The shared root cause is that no package owns the "DoStream transport message to typed business error" contract, so each domain package carries its own copy. Both copies also share the same cause-attachment gap: a classified error that is neither *errs.PermissionError nor *errs.APIError is returned without the original transport cause.

The repository guidelines ask you to fix root causes at the narrowest cohesive owner boundary and to reuse existing machinery. Two identical shims in two packages is the widest possible boundary for one contract. Consider moving the classifier and the hint constants into shortcuts/common, where RuntimeContext and ClassifyAPIResponse already live, and keeping only the domain-specific hint composition in each package. If you prefer to keep the shims local while DoStream lacks a structured body, record that decision in both file comments so the next reader knows the duplication is intentional and time-boxed.

  • shortcuts/doc/doc_errors.go#L33-L83: move classifyDocMediaStreamError to a shared helper, or document why the Doc copy must stay local.
  • shortcuts/drive/drive_errors.go#L40-L84: replace classifyDriveFileReadStreamError with the shared helper, or document the same rationale.
  • shortcuts/doc/doc_errors.go#L18-L21: source docMediaAppScopeHint and docMediaRateLimitHint from the shared location.
  • shortcuts/drive/drive_errors.go#L19-L21: source driveFileReadAppScopeHint and driveFileReadRateLimitHint from the same shared location.
🤖 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/doc/doc_errors.go` around lines 33 - 83, The duplicated
media-stream classifier and recovery-hint constants should be owned by
shortcuts/common, with the shared helper also attaching the original transport
cause to every classified error it returns. In
shortcuts/doc/doc_errors.go#L33-83 and shortcuts/drive/drive_errors.go#L40-84,
replace the local classifiers with the shared helper; in
shortcuts/doc/doc_errors.go#L18-21 and shortcuts/drive/drive_errors.go#L19-21,
source all four hints from the shared common definitions while preserving each
package’s domain-specific hint composition.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@shortcuts/doc/doc_errors.go`:
- Around line 102-124: Extract the duplicated app-scope and rate-limit hint
logic from withDocMediaDownloadRecoveryHint and withDocMediaPreviewRecoveryHint
into one shared helper. Have the helper perform the existing problem validation,
append both hints, and use the hint constants or shared guard values instead of
repeating literal fragments; then call it from both recovery functions while
preserving their current error return behavior.
- Around line 72-81: Update the error-classification flow around the
PermissionError and APIError handling so every classified error preserves the
original err as its cause. If classified is neither supported type, return or
wrap the original err using the established fallback rather than returning
classified without a cause, while preserving the existing typed-error behavior.
- Around line 33-83: The duplicated media-stream classifier and recovery-hint
constants should be owned by shortcuts/common, with the shared helper also
attaching the original transport cause to every classified error it returns. In
shortcuts/doc/doc_errors.go#L33-83 and shortcuts/drive/drive_errors.go#L40-84,
replace the local classifiers with the shared helper; in
shortcuts/doc/doc_errors.go#L18-21 and shortcuts/drive/drive_errors.go#L19-21,
source all four hints from the shared common definitions while preserving each
package’s domain-specific hint composition.

In `@shortcuts/doc/doc_media_test.go`:
- Around line 743-748: Remove the redundant strings.Contains(problem.Hint, "1
minute") assertions following exact hint equality checks in
shortcuts/doc/doc_media_test.go lines 743-748, shortcuts/drive/drive_io_test.go
lines 1832-1837, and shortcuts/drive/drive_io_test.go lines 1867-1872; retain
each golden constant comparison.
- Around line 879-903: Add log-ID coverage to the Doc media error test around
the HTTP stub and problem assertions: import the existing larkcore package, set
larkcore.HttpHeaderKeyLogId on the stub response using a test log ID, and assert
the resulting problem.LogID matches it, mirroring the Drive test while
preserving the existing error checks.

In `@shortcuts/drive/drive_errors.go`:
- Around line 123-136: Extract the shared app-scope and rate-limit hint logic
from withDrivePreviewRecoveryHint and withDriveDownloadRecoveryHint into one
helper, then have both recovery functions reuse it. Preserve the existing
guards, hint constants, and return behavior while removing the duplicated
literal checks.
- Around line 116-119: Check the success flag returned by errs.ProblemOf in both
the driveFileReadIsRateLimit path and withDrivePreviewRecoveryHint before
accessing problem.Hint. Only evaluate the exponential-backoff hint when
conversion succeeds; preserve the existing return behavior otherwise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 497813a9-8bcb-4928-aa31-c80614261149

📥 Commits

Reviewing files that changed from the base of the PR and between 115357d and 29f599e.

📒 Files selected for processing (9)
  • shortcuts/doc/doc_errors.go
  • shortcuts/doc/doc_media_download.go
  • shortcuts/doc/doc_media_preview.go
  • shortcuts/doc/doc_media_test.go
  • shortcuts/drive/drive_download.go
  • shortcuts/drive/drive_errors.go
  • shortcuts/drive/drive_io_test.go
  • shortcuts/drive/drive_preview_common.go
  • shortcuts/drive/drive_preview_test.go

@github-actions

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

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

🧩 Skill update

npx skills add wittam-01/cli#fix/media-stream-recovery-hints -y -g

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

Labels

domain/ccm PR touches the ccm domain size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant