Skip to content

feat(im): add recovery hint for cross-identity message resources#652

Open
chenxingtong-bytedance wants to merge 1 commit intolarksuite:mainfrom
chenxingtong-bytedance:fix/recognize_send_err
Open

feat(im): add recovery hint for cross-identity message resources#652
chenxingtong-bytedance wants to merge 1 commit intolarksuite:mainfrom
chenxingtong-bytedance:fix/recognize_send_err

Conversation

@chenxingtong-bytedance
Copy link
Copy Markdown
Contributor

@chenxingtong-bytedance chenxingtong-bytedance commented Apr 24, 2026

Change-Id: I8a43486333638271f0fbbcffca81a60c9f9d2060

Summary

This change improves recovery guidance for IM send/reply failures when referenced message resources belong to a different identity. It classifies the
ownership-mismatch API error explicitly and returns structured, actionable hints so users and AI agents can recover by downloading the original resource and
re-uploading it under the current identity.

Changes

  • Add explicit handling for the IM resource ownership mismatch error (231205), including a dedicated error type and clearer user-facing message formatting.
  • Add a step-by-step recovery hint for both direct media flags and structured JSON payload workflows.
  • Add tests covering error classification, recovery message formatting, and recovery hint completeness.

Test Plan

  • go test ./internal/output/...
  • Manual local verification confirms the lark im command works as expected

Related Issues

  • None

Summary by CodeRabbit

  • New Features

    • Improved handling for actions referencing resources owned by others: user-facing recovery message plus step-by-step instructions to download/upload resources and retry the operation.
  • Tests

    • Added tests validating ownership-mismatch classification, recovery message content, detailed hint instructions, and end-to-end error detail behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

Detects Lark error code 231205 (ownership mismatch), classifies it as ownership_mismatch/ExitAPI, substitutes a tailored ownership-recovery message into the error detail, and adds a multi-step hint via new ownership-recovery helpers; tests verify classification, message, hint, and ErrAPI integration.

Changes

Cohort / File(s) Summary
Lark error classification
internal/output/lark_errors.go
Adds LarkErrOwnershipMismatch = 231205 and maps it in ClassifyLarkError to ExitAPI with Type="ownership_mismatch" and a recovery hint.
API error handling
internal/output/errors.go
ErrAPI now detects LarkErrOwnershipMismatch and replaces the error detail Message with buildOwnershipRecoveryMessage(msg) (after existing normalization).
Ownership recovery helpers
internal/output/ownership_recovery.go
New helpers: ownershipRecoveryMessagePrefix, buildOwnershipRecoveryMessage(), and buildOwnershipRecoveryHint() that generate the prefixed message and multi-step recovery hint.
Tests
internal/output/lark_errors_test.go, internal/output/ownership_recovery_test.go
Adds a test vector for the new error code and comprehensive tests validating hint contents, message formatting, and ErrAPI integration preserving upstream detail.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

domain/im

Suggested reviewers

  • sang-neo03
  • sammi-bytedance
  • YangJunzhou-01

Poem

🐰 I found a mismatch, resources bound,
I nudged a hint so help is found.
Download, retry, or upload anew—
Follow the hops and send it through.
Tests cheer, recovery hops in view. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% 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
Title check ✅ Passed The title clearly summarizes the main change: adding recovery guidance for cross-identity message resource errors. It is concise, specific, and directly reflects the primary objective of the PR.
Description check ✅ Passed The description follows the template structure with Summary, Changes, Test Plan, and Related Issues sections. All required sections are present and populated with specific, meaningful information about the change.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label Apr 24, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
internal/output/ownership_recovery.go (1)

18-22: Optional: promote the hint to a package-level const.

buildOwnershipRecoveryHint() takes no inputs and always returns the same literal. A const (or var) avoids the per-call string concatenation and makes the content easier to diff/locate. Purely stylistic — no behavior change.

♻️ Proposed refactor
-func buildOwnershipRecoveryHint() string {
-	return "Step 1: ..." +
-		"Step 2: ..." +
-		"Step 3: ..."
-}
+const ownershipRecoveryHint = "Step 1: ...\n" +
+	"Step 2: ...\n" +
+	"Step 3: ..."
+
+func buildOwnershipRecoveryHint() string { return ownershipRecoveryHint }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/output/ownership_recovery.go` around lines 18 - 22,
buildOwnershipRecoveryHint always returns the same long literal so replace it
with a package-level constant (e.g., const OwnershipRecoveryHint = "...")
containing the full multi-line hint and remove or update the
buildOwnershipRecoveryHint() function to return that const (or delete the
function and update all callers to use OwnershipRecoveryHint). Ensure the
constant preserves the exact string content/escaping and update any imports/uses
referencing buildOwnershipRecoveryHint to reference OwnershipRecoveryHint to
avoid per-call concatenation overhead and make the text easier to locate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/core/types.go`:
- Around line 43-45: The default Feishu endpoints were accidentally changed to
the staging host (*.feishu-pre.cn); restore the public defaults by changing the
Open, Accounts, and MCP values back to "https://open.feishu.cn",
"https://accounts.feishu.cn", and "https://mcp.feishu.cn" in the BrandFeishu
defaults (internal/core/types.go) and mirror the same correction for the
permission-error console_url usage in cmd/root.go so unrecognized brands/users
use the production Feishu hosts.

In `@internal/output/ownership_recovery.go`:
- Line 8: The prefix ownershipRecoveryMessagePrefix is worded "message send
failed…" but LarkErrOwnershipMismatch can occur for both im+messages-send and
im+messages-reply; update the string to a neutral phrasing (e.g., "message
operation failed because one or more referenced resources belong to another
user") so it accurately applies to send and reply paths; change the constant
value in ownership_recovery.go (ownershipRecoveryMessagePrefix) and keep
references to LarkErrOwnershipMismatch intact.

---

Nitpick comments:
In `@internal/output/ownership_recovery.go`:
- Around line 18-22: buildOwnershipRecoveryHint always returns the same long
literal so replace it with a package-level constant (e.g., const
OwnershipRecoveryHint = "...") containing the full multi-line hint and remove or
update the buildOwnershipRecoveryHint() function to return that const (or delete
the function and update all callers to use OwnershipRecoveryHint). Ensure the
constant preserves the exact string content/escaping and update any imports/uses
referencing buildOwnershipRecoveryHint to reference OwnershipRecoveryHint to
avoid per-call concatenation overhead and make the text easier to locate.
🪄 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: 229a22d3-9859-44a4-bf4e-64bf21a3fadf

📥 Commits

Reviewing files that changed from the base of the PR and between fd4c35b and df0cf20.

📒 Files selected for processing (7)
  • cmd/root.go
  • internal/core/types.go
  • internal/output/errors.go
  • internal/output/lark_errors.go
  • internal/output/lark_errors_test.go
  • internal/output/ownership_recovery.go
  • internal/output/ownership_recovery_test.go

Comment thread internal/core/types.go Outdated
Comment thread internal/output/ownership_recovery.go Outdated
@github-actions github-actions Bot added size/M Single-domain feat or fix with limited business impact and removed size/L Large or sensitive change across domains or core paths labels Apr 24, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/output/errors.go (1)

97-102: Optional: key the rewrite off errType for consistency.

The preceding permission branch uses errType == "permission", while the new branch checks larkCode == LarkErrOwnershipMismatch. Both work today, but keying both on errType keeps the classification authoritative and avoids drift if another code is ever mapped to the ownership_mismatch type in ClassifyLarkError.

♻️ Proposed refactor
-	if larkCode == LarkErrOwnershipMismatch {
+	if errType == "ownership_mismatch" {
 		msg = buildOwnershipRecoveryMessage(msg)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/output/errors.go` around lines 97 - 102, The ownership-recovery
branch currently checks larkCode == LarkErrOwnershipMismatch which diverges from
the earlier errType-based check; change that conditional to check the classified
errType (e.g., errType == "ownership_mismatch") so both branches use the
authoritative classification from ClassifyLarkError, leaving the call to
buildOwnershipRecoveryMessage(msg) and the existing msg assignment unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/output/errors.go`:
- Around line 97-102: The ownership-recovery branch currently checks larkCode ==
LarkErrOwnershipMismatch which diverges from the earlier errType-based check;
change that conditional to check the classified errType (e.g., errType ==
"ownership_mismatch") so both branches use the authoritative classification from
ClassifyLarkError, leaving the call to buildOwnershipRecoveryMessage(msg) and
the existing msg assignment unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40b3a91b-81a7-40c7-aefc-2658d09baf3a

📥 Commits

Reviewing files that changed from the base of the PR and between df0cf20 and 02b293e.

📒 Files selected for processing (5)
  • internal/output/errors.go
  • internal/output/lark_errors.go
  • internal/output/lark_errors_test.go
  • internal/output/ownership_recovery.go
  • internal/output/ownership_recovery_test.go
✅ Files skipped from review due to trivial changes (1)
  • internal/output/lark_errors_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/output/ownership_recovery.go
  • internal/output/ownership_recovery_test.go

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/output/errors.go (1)

97-102: Optional: dispatch ownership-mismatch off errType for consistency.

The permission branch above keys off errType == "permission", while the new branch keys off the raw larkCode. Either style works, but switching to errType == "ownership_mismatch" would keep the message-rewrite dispatch uniform and make ErrAPI independent of which specific Lark codes fall under that classification. Non-blocking.

♻️ Suggested tweak
 	if errType == "permission" {
 		msg = fmt.Sprintf("Permission denied [%d]", larkCode)
 	}
-	if larkCode == LarkErrOwnershipMismatch {
+	if errType == "ownership_mismatch" {
 		msg = buildOwnershipRecoveryMessage(msg)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/output/errors.go` around lines 97 - 102, Change the
ownership-mismatch check to use the error classification string instead of the
numeric Lark code so message dispatch is consistent: replace the larkCode ==
LarkErrOwnershipMismatch branch with a check for errType == "ownership_mismatch"
and then call buildOwnershipRecoveryMessage(msg) there (keep the existing
permission check on errType == "permission"); this makes ErrAPI rely on errType
rather than specific Lark codes while preserving behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/output/errors.go`:
- Around line 97-102: Change the ownership-mismatch check to use the error
classification string instead of the numeric Lark code so message dispatch is
consistent: replace the larkCode == LarkErrOwnershipMismatch branch with a check
for errType == "ownership_mismatch" and then call
buildOwnershipRecoveryMessage(msg) there (keep the existing permission check on
errType == "permission"); this makes ErrAPI rely on errType rather than specific
Lark codes while preserving behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6653c6df-fd40-485e-9987-88ceb6b99718

📥 Commits

Reviewing files that changed from the base of the PR and between 02b293e and dd0c2c3.

📒 Files selected for processing (5)
  • internal/output/errors.go
  • internal/output/lark_errors.go
  • internal/output/lark_errors_test.go
  • internal/output/ownership_recovery.go
  • internal/output/ownership_recovery_test.go
✅ Files skipped from review due to trivial changes (2)
  • internal/output/lark_errors_test.go
  • internal/output/ownership_recovery.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/output/ownership_recovery_test.go

Change-Id: I8a43486333638271f0fbbcffca81a60c9f9d2060
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/output/ownership_recovery.go (1)

18-22: Optional: extract the hint steps into named constants or a slice joined with \n for readability.

The function body is a single concatenated string literal where each "Step N" line is many sentences long. A []string{...} joined with "\n" (or one const stepN = ... per step) would make future edits and diffs much easier to read without changing behavior.

♻️ Example refactor
 func buildOwnershipRecoveryHint() string {
-	return "Step 1: download each original resource only with ..." +
-		"Step 2: if the original command used direct media flags ..." +
-		"Step 3: if the original command used structured JSON ..."
+	steps := []string{
+		"Step 1: download each original resource only with `lark-cli im +messages-resources-download --message-id <message_id> --file-key <resource_key> --type <image|file> --output <local_path>`; use `--type image` for images and `--type file` for file/audio/video resources. `message_id` is mandatory for this download. Do not guess it, do not switch to any other download command or raw API, and do not keep retrying alternative download methods. If `message_id` is unavailable, stop and ask the user for it, or recover it from the original command context before continuing.",
+		"Step 2: if the original command used direct media flags (`--image`, `--file`, `--audio`, `--video`, `--video-cover`), retry with the downloaded local path instead of the old resource key; `im +messages-send` and `im +messages-reply` will upload the local file automatically under the current identity.",
+		"Step 3: if the original command used structured JSON (`--msg-type post`, `--msg-type interactive`, or `--content`), the downloaded output path cannot be embedded directly in JSON. Upload each resource first under the current identity (image -> `POST /open-apis/im/v1/images`, file/audio/video -> `POST /open-apis/im/v1/files`), replace the old `image_key` / `file_key` in JSON with the new keys, then retry the original send/reply. Repeat this for every referenced resource.",
+	}
+	return strings.Join(steps, "\n")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/output/ownership_recovery.go` around lines 18 - 22, The
buildOwnershipRecoveryHint function currently returns one long concatenated
string; extract each "Step N" into either separate constants (e.g., step1,
step2, step3) or into a []string{...} named steps and then return
strings.Join(steps, "\n") so the content is easier to read and diff; ensure the
final returned string remains identical and update buildOwnershipRecoveryHint to
reference the new constants/slice.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/output/ownership_recovery.go`:
- Around line 18-22: The buildOwnershipRecoveryHint function currently returns
one long concatenated string; extract each "Step N" into either separate
constants (e.g., step1, step2, step3) or into a []string{...} named steps and
then return strings.Join(steps, "\n") so the content is easier to read and diff;
ensure the final returned string remains identical and update
buildOwnershipRecoveryHint to reference the new constants/slice.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 314bdee4-d3cf-4d01-9a40-4990847c53fa

📥 Commits

Reviewing files that changed from the base of the PR and between dd0c2c3 and 564dd05.

📒 Files selected for processing (5)
  • internal/output/errors.go
  • internal/output/lark_errors.go
  • internal/output/lark_errors_test.go
  • internal/output/ownership_recovery.go
  • internal/output/ownership_recovery_test.go
✅ Files skipped from review due to trivial changes (1)
  • internal/output/lark_errors_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/output/lark_errors.go
  • internal/output/errors.go
  • internal/output/ownership_recovery_test.go

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

Labels

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