Skip to content

feat(im): return typed error envelopes across the im domain#1230

Open
evandance wants to merge 1 commit into
mainfrom
feat/errs-migrate-im
Open

feat(im): return typed error envelopes across the im domain#1230
evandance wants to merge 1 commit into
mainfrom
feat/errs-migrate-im

Conversation

@evandance
Copy link
Copy Markdown
Collaborator

@evandance evandance commented Jun 2, 2026

Summary

shortcuts/im returned errors through legacy output.Err* / fmt.Errorf, so consumers saw free-form messages with no machine-readable classification and AI agents had to regex error text to recover. This migrates the im domain to typed errs.* envelopes, giving callers stable type, subtype, param, and missing_scopes fields, and corrects error paths that previously fell into the generic internal exit code.

Changes

  • Migrate all produced errors in shortcuts/im/*.go (chat, message, flag, thread, mute, resource-download) from output.Err* / output.Errorf / final fmt.Errorf to typed errs.* builders
  • Add shortcuts/im/im_errors.go with wrapIMNetworkErr (pass typed errors through, wrap raw ones as transport-level network errors) and imSaveError (path-validation → validation, mkdir/write → internal file-I/O)
  • Classify missing-scope precheck as *errs.PermissionError with a structured missing_scopes array; classify derived "not found" lookups as *errs.APIError{not_found}
  • Reclassify previously untyped URL-download / upload failures from the generic internal fallback to validation or network as appropriate
  • Attribute resource-download path errors to the correct flag (--file-key vs --output) via normalizeDownloadOutputPath
  • Enforce typed-only errors on shortcuts/im via forbidigo (.golangci.yml) to prevent reintroduction of legacy constructors
  • A few combination/precondition validation sites carry // TODO(errs) markers to adopt errs.WithParams / errs.SubtypeFailedPrecondition once those primitives land on main; until then they use invalid_argument

Test Plan

  • make unit-test passed (shortcuts/im, shortcuts/im/convert_lib, internal/errclass)
  • validate passed (build + vet + unit/integration)
  • local-eval passed (E2E 3/3 runnable, 2 skipped pending auth fixtures; skillave N/A — no command-surface change)
  • acceptance-reviewer passed (6/6 scenarios)
  • golangci-lint --new-from-rev=origin/main: 0 issues
  • manual verification: im +messages-resources-download --message-id om_x --type file --file-key a/b → exit 2, error.type=validation, error.param=--file-key; absolute --outputerror.param=--output

Related Issues

N/A

Summary by CodeRabbit

  • Bug Fixes & Improvements

    • Enhanced error messages across messaging features with clearer parameter attribution and error context
    • Improved error categorization for validation failures, authentication issues, and network problems
    • Better consistency in error reporting for chat and message operations
  • Tests

    • Added tests to verify error handling and wrapping behavior

Migrate the produced errors in shortcuts/im from legacy output.Err*
and fmt.Errorf to typed errs.* builders, so consumers get stable
category and subtype fields (plus param and missing_scopes where
applicable) for classification and recovery instead of free-form
message text. Previously untyped download/upload failures are now
classified as validation or network instead of the generic internal
fallback. Genuine intermediate %w wraps are left untouched.

Ban legacy output.Err* on shortcuts/im via forbidigo to prevent
reintroduction.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the IM shortcuts package from legacy output and fmt.Errorf error handling to a structured errs API with typed validation, network, authentication, permission, API, and internal errors. New helpers in im_errors.go wrap raw errors as typed errors while preserving already-typed errors. The linter configuration expands path exemptions to permit the migrated code.

Changes

IM Shortcuts Error Handling Migration

Layer / File(s) Summary
Error Infrastructure & Helper Functions
shortcuts/im/im_errors.go, shortcuts/im/im_errors_test.go
wrapIMNetworkErr and imSaveError helper functions introduced to conditionally wrap raw errors as typed network/validation/internal errors and map fileio failures to appropriate error types, with unit tests verifying wrapping behavior and error-type preservation.
Core Helpers File Migration
shortcuts/im/helpers.go
helpers.go refactored across 22+ helper functions to replace output.*/fmt.Errorf with errs package calls. Validation errors use errs.NewValidationError(...).WithParam(...), network errors use errs.NewNetworkError(...), API not-found use errs.NewAPIError(..., SubtypeNotFound, ...), auth/scope failures use errs.NewAuthenticationError(...) and errs.NewPermissionError(...), and parse/response errors use errs.NewInternalError(...).WithCause(...).
Chat & Flag Shortcut Migrations
shortcuts/im/im_chat_*.go, shortcuts/im/im_flag_*.go, shortcuts/im/im_flag_test.go
9 shortcut files migrate validation error handling from output.ErrValidation to errs.NewValidationError(...).WithParam(...) with structured parameter attribution. Flag validation tests refactored to assert typed errs.*Error shapes instead of generic exit codes.
Message Shortcut Migrations
shortcuts/im/im_messages_*.go (except download), shortcuts/im/im_threads_messages_list.go
5 message shortcut files migrate validation errors to errs.NewValidationError(...).WithParam(...) for argument bounds, flag validity, and content checks with consistent parameter attribution via .WithParam(flagName).
Resource Download Migration
shortcuts/im/im_messages_resources_download.go
Comprehensive refactor replacing output.ErrValidation/ErrNetwork with errs.NewValidationError for path/argument validation and errs.NewNetworkError for transport failures (chunk overflow, size mismatch, HTTP errors, Content-Range parsing, retry exhaustion). File-IO save errors mapped via new imSaveError helper.
Mute Filter & Linter Configuration
shortcuts/im/mute_filter.go, .golangci.yml
mute_filter.go switches batch-size validation to errs.NewValidationError and API failures to wrapIMNetworkErr. .golangci.yml expands forbidigo path-except regex to exempt migrated IM shortcuts and internal error packages from print-statement restrictions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • larksuite/cli#1135: Both PRs address lint enforcement in .golangci.yml for forbidigo path-exemption expansion, with the main PR then performing the corresponding shortcuts/im/* migration to typed-error builders consistent with that config.
  • larksuite/cli#820: Both PRs touch IM chat shortcuts (especially im_chat_list.go and im_chat_search.go) with the main PR migrating validation/error paths to the errs package while the retrieved PR introduces new chat list features.
  • larksuite/cli#322: Both PRs modify IM media/file helper logic in shortcuts/im/helpers.go to refactor error wrapping and local file IO validation, with the main PR using errs-typed errors and the retrieved PR using fileio-typed errors.

Suggested labels

domain/im, size/L

Suggested reviewers

  • liangshuo-1
  • YangJunzhou-01
  • jackie3927

Poem

🐰 Error types now structured, no more strings astray,
Validation hints and causes brighten the way,
From format chaos to errs so neat,
The IM package migration is complete! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 PR title accurately summarizes the primary change: migrating the im domain to return typed error envelopes instead of legacy free-form errors.
Description check ✅ Passed The description follows the template structure with Summary, Changes, Test Plan, and Related Issues sections. All required sections are present and well-populated with specific details.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/errs-migrate-im

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 domain/im PR touches the im domain size/L Large or sensitive change across domains or core paths labels Jun 2, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 2, 2026

Codecov Report

❌ Patch coverage is 55.40541% with 66 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.23%. Comparing base (04932c2) to head (a8c7535).

Files with missing lines Patch % Lines
shortcuts/im/helpers.go 47.72% 23 Missing ⚠️
shortcuts/im/im_messages_resources_download.go 42.85% 12 Missing ⚠️
shortcuts/im/im_flag_create.go 61.53% 5 Missing ⚠️
shortcuts/im/im_chat_create.go 42.85% 4 Missing ⚠️
shortcuts/im/im_messages_reply.go 0.00% 4 Missing ⚠️
shortcuts/im/im_chat_search.go 40.00% 3 Missing ⚠️
shortcuts/im/im_flag_cancel.go 57.14% 3 Missing ⚠️
shortcuts/im/im_messages_search.go 50.00% 3 Missing ⚠️
shortcuts/im/im_chat_messages_list.go 66.66% 2 Missing ⚠️
shortcuts/im/im_errors.go 86.66% 2 Missing ⚠️
... and 5 more
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1230   +/-   ##
=======================================
  Coverage   69.22%   69.23%           
=======================================
  Files         635      636    +1     
  Lines       59687    59702   +15     
=======================================
+ Hits        41320    41333   +13     
- Misses      15033    15035    +2     
  Partials     3334     3334           

☔ View full report in Codecov by Sentry.
📢 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.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

🚀 PR Preview Install Guide

🧰 CLI update

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

🧩 Skill update

npx skills add larksuite/cli#feat/errs-migrate-im -y -g

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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
shortcuts/im/im_messages_send.go (1)

218-224: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Treat missing local media files as invalid input.

Ignoring os.IsNotExist(err) lets --image/--file/--video/--audio point at a missing local path without producing the typed validation envelope this migration is adding. runtime.FileIO().Stat() already handles path-safety for these flags, so the remaining Stat errors should be surfaced here too.

Suggested fix
 func validateMediaFlagPath(fio fileio.FileIO, flagName, value string) error {
 	if value == "" || strings.HasPrefix(value, "http://") || strings.HasPrefix(value, "https://") || isMediaKey(value) {
 		return nil
 	}
-	if _, err := fio.Stat(value); err != nil && !os.IsNotExist(err) {
+	if _, err := fio.Stat(value); err != nil {
 		return errs.NewValidationError(errs.SubtypeInvalidArgument, "%s: %v", flagName, err).WithParam(flagName)
 	}
 	return nil
 }

Based on learnings, runtime.FileIO().Stat() already enforces path-safety for user-supplied local file paths, so suppressing Stat failures here only hides missing-file input errors.

🤖 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_send.go` around lines 218 - 224, The
validateMediaFlagPath function currently ignores os.IsNotExist errors from
fio.Stat, allowing missing local media paths to pass validation; update the
error handling in validateMediaFlagPath so that any Stat error (including
os.IsNotExist) is returned as a typed validation error using
errs.NewValidationError with flagName context (i.e., replace the conditional
that skips os.IsNotExist with a straight check for err != nil and return the
validation error), preserving the initial checks for empty/HTTP/media-key
values.
shortcuts/im/im_messages_resources_download.go (1)

400-419: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrap the final transport error before returning it.

After the retry loop, lastErr is returned verbatim. If DoAPIStream keeps failing with a raw timeout/DNS/socket error, callers lose the typed IM envelope on the exact path this PR is migrating.

Suggested fix
 	if lastErr != nil {
-		return nil, lastErr
+		return nil, wrapIMNetworkErr(lastErr)
 	}
 	return nil, errs.NewNetworkError(errs.SubtypeNetworkTransport, "download request failed")
 }
🤖 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 400 - 419, The
loop currently returns lastErr verbatim which loses the IM-specific network
envelope; instead wrap lastErr in the IM network error before returning so
callers keep the typed envelope. Replace the final "return lastErr" with a
wrapped error (e.g. return errs.NewNetworkError(errs.SubtypeNetworkTransport,
"download request failed", lastErr) or otherwise attach lastErr as the cause),
leaving the retry logic using DoAPIStream, imDownloadRequestRetries and
sleepIMDownloadRetry unchanged; ensure the final return uses
errs.NewNetworkError with lastErr included.
shortcuts/im/im_messages_search.go (1)

360-366: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject oversized --page-size values instead of silently clamping.

This now returns a typed invalid-argument error for page-size < 1, but --page-size > 50 still succeeds and is quietly rewritten to 50. That makes the new validation contract asymmetric and hides caller mistakes.

Suggested fix
 	pageSize := runtime.Int("page-size")
-	if pageSize < 1 {
+	if pageSize < 1 || pageSize > messagesSearchMaxPageSize {
 		return nil, errs.NewValidationError(errs.SubtypeInvalidArgument, "--page-size must be an integer between 1 and 50").WithParam("--page-size")
 	}
-	if pageSize > messagesSearchMaxPageSize {
-		pageSize = messagesSearchMaxPageSize
-	}
🤖 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_search.go` around lines 360 - 366, The code
currently clamps pageSize > messagesSearchMaxPageSize to 50; change this to
return a validation error instead so the contract is symmetric. In the function
that reads pageSize (the runtime.Int("page-size") branch), replace the clamp
block that references messagesSearchMaxPageSize with a check that if pageSize >
messagesSearchMaxPageSize you return
errs.NewValidationError(errs.SubtypeInvalidArgument, "--page-size must be an
integer between 1 and 50").WithParam("--page-size") (matching the existing
pageSize < 1 error) so oversized values are rejected rather than silently
rewritten.
🤖 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/im/helpers.go`:
- Around line 1052-1055: The validation errors for oversize uploads lose
structured attribution because upload helpers (e.g., uploadImageToIM) don't
include the originating flag; update the upload helper signatures to accept a
flagName string (e.g., add flagName param to uploadImageToIM and the other
upload helpers referenced around lines 1089–1092), pass s.flagName from
resolveLocalMedia into those helpers, and include that flagName as the param on
errs.NewValidationError calls so the error includes the source flag (use
s.flagName when calling the updated upload functions and set the param field in
the validation error construction).
- Around line 1371-1384: Change the error types returned from the API lookup:
when items is missing or empty (the items := data["items"].([]any) check) return
errs.NewAPIError(errs.SubtypeNotFound, ...) instead of errs.NewValidationError;
for malformed payload shapes — the msg type assertion
(items[0].(map[string]any)) and missing chat_id (msg["chat_id"].(string)) —
return errs.NewInternalError(errs.SubtypeInvalidResponse, ...) instead of
errs.NewValidationError so downstream callers get NotFound for lookups and
Internal/InvalidResponse for bad API payloads.

In `@shortcuts/im/im_errors.go`:
- Around line 32-33: The ValidationError returned when errors.Is(err,
fileio.ErrPathValidation) currently drops the param field; update the return in
im_errors.go for the fileio.ErrPathValidation case so it preserves the flag by
chaining .WithParam("--output") on the error (i.e., return
errs.NewValidationError(errs.SubtypeInvalidArgument, "unsafe output path: %s",
err).WithParam("--output").WithCause(err)), referencing the existing
errors.Is(err, fileio.ErrPathValidation) branch and the NewValidationError call.

In `@shortcuts/im/im_flag_create.go`:
- Around line 140-145: The current code around getMessageChatID (and likewise
resolveThreadFeedItemType at lines ~151-155) indiscriminately wraps all errors
into errs.NewValidationError, which hides upstream categories; change the logic
to return upstream errors unchanged unless the failure truly indicates invalid
caller input—i.e., detect the specific condition that means the caller must
change input and only then synthesize a ValidationError (using
errs.NewValidationError). For all other errors from getMessageChatID and
resolveThreadFeedItemType, propagate the original error (or map through the IM
network wrapper) instead of wrapping it into a ValidationError so
transport/auth/network failures keep their original types.

---

Outside diff comments:
In `@shortcuts/im/im_messages_resources_download.go`:
- Around line 400-419: The loop currently returns lastErr verbatim which loses
the IM-specific network envelope; instead wrap lastErr in the IM network error
before returning so callers keep the typed envelope. Replace the final "return
lastErr" with a wrapped error (e.g. return
errs.NewNetworkError(errs.SubtypeNetworkTransport, "download request failed",
lastErr) or otherwise attach lastErr as the cause), leaving the retry logic
using DoAPIStream, imDownloadRequestRetries and sleepIMDownloadRetry unchanged;
ensure the final return uses errs.NewNetworkError with lastErr included.

In `@shortcuts/im/im_messages_search.go`:
- Around line 360-366: The code currently clamps pageSize >
messagesSearchMaxPageSize to 50; change this to return a validation error
instead so the contract is symmetric. In the function that reads pageSize (the
runtime.Int("page-size") branch), replace the clamp block that references
messagesSearchMaxPageSize with a check that if pageSize >
messagesSearchMaxPageSize you return
errs.NewValidationError(errs.SubtypeInvalidArgument, "--page-size must be an
integer between 1 and 50").WithParam("--page-size") (matching the existing
pageSize < 1 error) so oversized values are rejected rather than silently
rewritten.

In `@shortcuts/im/im_messages_send.go`:
- Around line 218-224: The validateMediaFlagPath function currently ignores
os.IsNotExist errors from fio.Stat, allowing missing local media paths to pass
validation; update the error handling in validateMediaFlagPath so that any Stat
error (including os.IsNotExist) is returned as a typed validation error using
errs.NewValidationError with flagName context (i.e., replace the conditional
that skips os.IsNotExist with a straight check for err != nil and return the
validation error), preserving the initial checks for empty/HTTP/media-key
values.
🪄 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: 955ef48f-2fa5-4305-bc24-030443534482

📥 Commits

Reviewing files that changed from the base of the PR and between 04932c2 and a8c7535.

📒 Files selected for processing (20)
  • .golangci.yml
  • shortcuts/im/helpers.go
  • shortcuts/im/im_chat_create.go
  • shortcuts/im/im_chat_list.go
  • shortcuts/im/im_chat_messages_list.go
  • shortcuts/im/im_chat_search.go
  • shortcuts/im/im_chat_update.go
  • shortcuts/im/im_errors.go
  • shortcuts/im/im_errors_test.go
  • shortcuts/im/im_flag_cancel.go
  • shortcuts/im/im_flag_create.go
  • shortcuts/im/im_flag_list.go
  • shortcuts/im/im_flag_test.go
  • shortcuts/im/im_messages_mget.go
  • shortcuts/im/im_messages_reply.go
  • shortcuts/im/im_messages_resources_download.go
  • shortcuts/im/im_messages_search.go
  • shortcuts/im/im_messages_send.go
  • shortcuts/im/im_threads_messages_list.go
  • shortcuts/im/mute_filter.go

Comment thread shortcuts/im/helpers.go
Comment on lines 1052 to 1055
func uploadImageToIM(ctx context.Context, runtime *common.RuntimeContext, filePath, imageType string) (string, error) {
if info, err := runtime.FileIO().Stat(filePath); err == nil && info.Size() > maxImageUploadSize {
return "", fmt.Errorf("image size %s exceeds limit (max 5MB)", common.FormatSize(info.Size()))
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "image size %s exceeds limit (max 5MB)", common.FormatSize(info.Size()))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the source flag on upload size validation errors.

Lines 1054 and 1091 emit typed validation errors without param, even though the failing input came from a specific flag. resolveLocalMedia already has s.flagName, so oversize --image, --video-cover, --file, --audio, or --video inputs currently lose structured attribution.

Suggested direction
-func uploadImageToIM(ctx context.Context, runtime *common.RuntimeContext, filePath, imageType string) (string, error) {
+func uploadImageToIM(ctx context.Context, runtime *common.RuntimeContext, filePath, imageType, param string) (string, error) {
 	if info, err := runtime.FileIO().Stat(filePath); err == nil && info.Size() > maxImageUploadSize {
-		return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "image size %s exceeds limit (max 5MB)", common.FormatSize(info.Size()))
+		return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "image size %s exceeds limit (max 5MB)", common.FormatSize(info.Size())).
+			WithParam(param)
 	}
 	...
 }
 
-func uploadFileToIM(ctx context.Context, runtime *common.RuntimeContext, filePath, fileType, duration string) (string, error) {
+func uploadFileToIM(ctx context.Context, runtime *common.RuntimeContext, filePath, fileType, duration, param string) (string, error) {
 	if info, err := runtime.FileIO().Stat(filePath); err == nil && info.Size() > maxFileUploadSize {
-		return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "file size %s exceeds limit (max 100MB)", common.FormatSize(info.Size()))
+		return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "file size %s exceeds limit (max 100MB)", common.FormatSize(info.Size())).
+			WithParam(param)
 	}
 	...
 }

Then pass s.flagName from resolveLocalMedia.

Also applies to: 1089-1092

🤖 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/helpers.go` around lines 1052 - 1055, The validation errors for
oversize uploads lose structured attribution because upload helpers (e.g.,
uploadImageToIM) don't include the originating flag; update the upload helper
signatures to accept a flagName string (e.g., add flagName param to
uploadImageToIM and the other upload helpers referenced around lines 1089–1092),
pass s.flagName from resolveLocalMedia into those helpers, and include that
flagName as the param on errs.NewValidationError calls so the error includes the
source flag (use s.flagName when calling the updated upload functions and set
the param field in the validation error construction).

Comment thread shortcuts/im/helpers.go
Comment on lines 1371 to 1384
items, ok := data["items"].([]any)
if !ok || len(items) == 0 {
return "", output.ErrValidation("message not found or unexpected API response format")
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "message not found or unexpected API response format")
}

msg, ok := items[0].(map[string]any)
if !ok {
return "", output.ErrValidation("unexpected message format in API response")
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "unexpected message format in API response")
}

chatID, ok := msg["chat_id"].(string)
if !ok {
return "", output.ErrValidation("message response missing chat_id field")
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "message response missing chat_id field")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t classify lookup/API-shape failures as invalid arguments.

Lines 1372-1384 turn both “message not found” and malformed message payloads into ValidationError. This helper is doing a derived API lookup from an already-validated message ID, so an empty items result should be errs.NewAPIError(errs.SubtypeNotFound, ...), while bad payload shapes or missing chat_id should be errs.NewInternalError(errs.SubtypeInvalidResponse, ...). Keeping them as validation changes the typed category and exit-code behavior for downstream callers.

Suggested fix
 	items, ok := data["items"].([]any)
 	if !ok || len(items) == 0 {
-		return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "message not found or unexpected API response format")
+		return "", errs.NewAPIError(errs.SubtypeNotFound, "message not found")
 	}
 
 	msg, ok := items[0].(map[string]any)
 	if !ok {
-		return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "unexpected message format in API response")
+		return "", errs.NewInternalError(errs.SubtypeInvalidResponse, "unexpected message format in API response")
 	}
 
 	chatID, ok := msg["chat_id"].(string)
 	if !ok {
-		return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "message response missing chat_id field")
+		return "", errs.NewInternalError(errs.SubtypeInvalidResponse, "message response missing chat_id 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/im/helpers.go` around lines 1371 - 1384, Change the error types
returned from the API lookup: when items is missing or empty (the items :=
data["items"].([]any) check) return errs.NewAPIError(errs.SubtypeNotFound, ...)
instead of errs.NewValidationError; for malformed payload shapes — the msg type
assertion (items[0].(map[string]any)) and missing chat_id
(msg["chat_id"].(string)) — return
errs.NewInternalError(errs.SubtypeInvalidResponse, ...) instead of
errs.NewValidationError so downstream callers get NotFound for lookups and
Internal/InvalidResponse for bad API payloads.

Comment thread shortcuts/im/im_errors.go
Comment on lines +32 to +33
case errors.Is(err, fileio.ErrPathValidation):
return errs.NewValidationError(errs.SubtypeInvalidArgument, "unsafe output path: %s", err).WithCause(err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve --output on path-validation save errors.

Line 32 drops the structured param field when runtime.FileIO().Save(...) rejects an unsafe output path. im_messages_resources_download.go routes save failures through this helper, and internal/client/response.go already classifies the same condition as ValidationError.WithParam("--output"), so callers would lose flag attribution here.

Suggested fix
 	case errors.Is(err, fileio.ErrPathValidation):
-		return errs.NewValidationError(errs.SubtypeInvalidArgument, "unsafe output path: %s", err).WithCause(err)
+		return errs.NewValidationError(errs.SubtypeInvalidArgument, "unsafe output path: %s", err).
+			WithParam("--output").
+			WithCause(err)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case errors.Is(err, fileio.ErrPathValidation):
return errs.NewValidationError(errs.SubtypeInvalidArgument, "unsafe output path: %s", err).WithCause(err)
case errors.Is(err, fileio.ErrPathValidation):
return errs.NewValidationError(errs.SubtypeInvalidArgument, "unsafe output path: %s", err).
WithParam("--output").
WithCause(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/im/im_errors.go` around lines 32 - 33, The ValidationError returned
when errors.Is(err, fileio.ErrPathValidation) currently drops the param field;
update the return in im_errors.go for the fileio.ErrPathValidation case so it
preserves the flag by chaining .WithParam("--output") on the error (i.e., return
errs.NewValidationError(errs.SubtypeInvalidArgument, "unsafe output path: %s",
err).WithParam("--output").WithCause(err)), referencing the existing
errors.Is(err, fileio.ErrPathValidation) branch and the NewValidationError call.

Comment on lines 140 to 145
chatID, err := getMessageChatID(rt, id)
if err != nil {
return flagItem{}, output.ErrValidation(
"failed to query message for feed-layer flag: %v; if you know the chat type, specify --item-type explicitly", err)
// TODO(errs): this is failed_precondition semantics (ambiguous mapping, caller must change input); upgrade to errs.SubtypeFailedPrecondition once it lands on main
return flagItem{}, errs.NewValidationError(errs.SubtypeInvalidArgument,
"failed to query message for feed-layer flag: %v", err).WithHint("specify --item-type explicitly").WithCause(err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve upstream lookup error categories here.

getMessageChatID and resolveThreadFeedItemType are prerequisite IM lookups, so their failures are not always invalid user input. Rewrapping every error as ValidationError turns network/API/auth failures into exit-code-2 validation envelopes and hides the real category from callers. Please return existing typed errors as-is (or route raw transport failures through the new IM network wrapper) and only synthesize a validation/precondition error for the genuine “caller must change input” cases.

Also applies to: 151-155

🤖 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_flag_create.go` around lines 140 - 145, The current code
around getMessageChatID (and likewise resolveThreadFeedItemType at lines
~151-155) indiscriminately wraps all errors into errs.NewValidationError, which
hides upstream categories; change the logic to return upstream errors unchanged
unless the failure truly indicates invalid caller input—i.e., detect the
specific condition that means the caller must change input and only then
synthesize a ValidationError (using errs.NewValidationError). For all other
errors from getMessageChatID and resolveThreadFeedItemType, propagate the
original error (or map through the IM network wrapper) instead of wrapping it
into a ValidationError so transport/auth/network failures keep their original
types.

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 feature size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant