Skip to content

Add mail message received event consumption#1687

Open
sysuljx wants to merge 3 commits into
larksuite:mainfrom
sysuljx:feat/5785bd0
Open

Add mail message received event consumption#1687
sysuljx wants to merge 3 commits into
larksuite:mainfrom
sysuljx:feat/5785bd0

Conversation

@sysuljx

@sysuljx sysuljx commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

This adds first-class event consumption support for newly received mail messages and keeps the existing mail +watch shortcut compatible with the new event flow.

  • Register the mail message received event and parameter normalization.
  • Add subscription setup, mailbox matching, message filtering, and output formatting.
  • Route mail +watch through the shared event consumer behavior.
  • Cover the new mail event flow and wrapper behavior with focused tests and docs updates.

Summary by CodeRabbit

  • New Features
    • Added unified mail.message_received_v1 event consumption with mailbox/label/folder filtering and msg_format output modes.
    • Upgraded mail +watch to the unified event-consume flow, adding --max-events and --timeout, and keeping legacy output compatibility.
  • Documentation
    • Updated mail watch guides and examples to prioritize event consume usage and clarify output envelopes/permissions.
  • Bug Fixes
    • Improved safe message output handling, including prompt-injection detection and more reliable event/message filtering behavior.
  • Tests
    • Added/extended tests for normalization, subscription lifecycle, matching, output formatting, and preflight control flow.

@github-actions github-actions Bot added domain/mail PR touches the mail domain size/L Large or sensitive change across domains or core paths labels Jun 30, 2026
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the mail.message_received_v1 event handler, registers it globally, routes mail +watch through event consume, adds consumer preflight wiring, and updates tests plus mail-related skill/reference documentation.

Changes

Mail Event Consume Integration

Layer / File(s) Summary
Event key definition, normalization, and matching
shortcuts/mail/mail_message_received_event.go
Defines MailMessageReceivedEventKey, MailMessageReceivedOutput, and registers mail.message_received_v1 with parameter definitions, normalization, subscription, and mailbox matching handlers.
Processing, lookup, and output shaping
shortcuts/mail/mail_message_received_event.go
Implements message processing, payload decoding, legacy wrapping, mailbox lookup, message fetch helpers, resolved-ID computation, folder and label listing, CSV parsing, and prompt-safe output annotation.
Event key registry wiring
events/mail/register.go, events/register.go
Adds the mail event registry helper and includes mail.Keys() in the global event registration list.
mail +watch metadata and entrypoint
shortcuts/mail/mail_watch.go
Updates MailWatch descriptions and flags, changes the dry-run step text, and replaces the inline WebSocket execution path with a call to the event-consume wrapper.
Event-consume wrapper and runtime adapter
shortcuts/mail/mail_watch_event_wrapper.go
Builds the mail event-consume invocation, ensures event keys are registered, and adapts API calls and error handling for the consume runtime.
Consumer preflight wiring
cmd/event/consume.go, internal/event/consume/consume.go, internal/event/consume/consume_test.go
Moves console preflight checks into consume.Options.Preflight, adds the new hook to internal/event/consume, and covers normalization-before-preflight control flow in tests.
Event handler tests
shortcuts/mail/mail_message_received_event_test.go
Adds the stub API client and test coverage for key wiring, normalization, subscription, matching, processing, output formats, and consume-args generation.
Skill and reference documentation updates
skills/lark-event/SKILL.md, skills/lark-mail/SKILL.md, skills/lark-mail/references/lark-mail-watch.md
Updates skill examples, topic index, shortcut guidance, and mail watch reference docs to describe the event-consume flow, parameter model, and output modes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • larksuite/cli#198: Modifies the same mail watch mailbox-resolution and event-filtering path.

Suggested reviewers

  • infeng
  • chanthuang

🐇 I hopped to the mailbox light,
mail.message_received_v1 now streams just right.
Keys and consume paths line up neat,
Folders and labels keep rhythm in beat.
A rabbit’s tail twitches: the docs are in tune,
and watch becomes consume by the end of the moon.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding consumption support for the mail message received event.
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.

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown

PR Quality Summary

CI did not complete successfully. Use the failed check links below to decide whether this PR needs a code change or a rerun.

Failed checks

@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 (2)
skills/lark-mail/references/lark-mail-watch.md (1)

6-7: 📐 Maintainability & Code Quality | 🔵 Trivial

Clarify the event key name distinction.

Line 6 parenthetically mentions mail.user_mailbox.event.message_received_v1 (the raw Lark platform event type), while Line 7 and all examples use mail.message_received_v1 (the CLI EventKey). Consider adding a brief note that the former is the server-side event type and the latter is the CLI-normalized key, to prevent users from attempting to consume using the full platform name.

🤖 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-mail/references/lark-mail-watch.md` around lines 6 - 7, Clarify
the event key naming in the Lark mail watch docs by explicitly distinguishing
the raw platform event type from the CLI-normalized EventKey. Update the wording
around the `mail.user_mailbox.event.message_received_v1` mention and the
`mail.message_received_v1` examples so readers know the former is the
server-side event type and the latter is what `event consume` expects; use the
`mail +watch` and `event consume` references to anchor the explanation.
shortcuts/mail/mail_watch.go (1)

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

Remove the unreachable legacy watch implementation below this return.

Line 187 makes the rest of Execute unreachable, but the old inline WebSocket path remains in lines 189-496. Please delete the dead block and any now-unused helpers/imports so there is only one mail watch implementation to maintain.

🤖 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/mail/mail_watch.go` around lines 187 - 188, The `Execute` flow in
`mail_watch.go` now returns from `runMailWatchViaEventConsume`, so the legacy
inline WebSocket implementation below that return is dead code. Remove the
unreachable block in `Execute` and delete any now-unused mail watch helpers and
imports tied to the old path so only the `runMailWatchViaEventConsume`-based
implementation remains.
🤖 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/mail/mail_message_received_event_test.go`:
- Around line 60-215: Add error-path coverage for the new mail handlers by
testing normalizeMailMessageReceivedParams, preConsumeMailMessageReceived, and
processMailMessageReceived with invalid format input, API failures, and wrapped
downstream errors. For each failure, assert the typed metadata with
errs.ProblemOf (category/subtype) and verify the original cause is preserved;
when checking Param on validation errors, use errors.As into the validation
error type since errs.ProblemOf does not expose Param.

In `@shortcuts/mail/mail_message_received_event.go`:
- Around line 185-187: The msg_format=event branch in
mail_message_received_event.go returns the raw payload directly, which bypasses
the registered MailMessageReceivedOutput shape. Update the event path in the
mail message received handler so marshalMailEventOutput (or the caller in
MailMessageReceivedOutput handling) wraps the decoded payload under the Event
field, keeping it consistent with the fetched-message path and the schema’s
Event/Message structure.

In `@shortcuts/mail/mail_watch_event_wrapper.go`:
- Around line 44-49: Reject negative values for the timeout and max-events flags
by adding validation in the mail watch wrapper before consume.Run is invoked;
the current timeout parsing in the mail validation path accepts values like
"-1s", which should be treated as invalid alongside negative max-events. Update
the validation logic around runtime.Str("timeout"), the max-events
parsing/handling, and mailValidationParamError so both flags fail fast with
typed validation metadata, and add tests covering the error paths for negative
inputs and the expected validation cause.

---

Nitpick comments:
In `@shortcuts/mail/mail_watch.go`:
- Around line 187-188: The `Execute` flow in `mail_watch.go` now returns from
`runMailWatchViaEventConsume`, so the legacy inline WebSocket implementation
below that return is dead code. Remove the unreachable block in `Execute` and
delete any now-unused mail watch helpers and imports tied to the old path so
only the `runMailWatchViaEventConsume`-based implementation remains.

In `@skills/lark-mail/references/lark-mail-watch.md`:
- Around line 6-7: Clarify the event key naming in the Lark mail watch docs by
explicitly distinguishing the raw platform event type from the CLI-normalized
EventKey. Update the wording around the
`mail.user_mailbox.event.message_received_v1` mention and the
`mail.message_received_v1` examples so readers know the former is the
server-side event type and the latter is what `event consume` expects; use the
`mail +watch` and `event consume` references to anchor the explanation.
🪄 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: c1eb1695-eb5f-44c0-ab7b-e891964db854

📥 Commits

Reviewing files that changed from the base of the PR and between 3fcb695 and 9cea436.

📒 Files selected for processing (9)
  • events/mail/register.go
  • events/register.go
  • shortcuts/mail/mail_message_received_event.go
  • shortcuts/mail/mail_message_received_event_test.go
  • shortcuts/mail/mail_watch.go
  • shortcuts/mail/mail_watch_event_wrapper.go
  • skills/lark-event/SKILL.md
  • skills/lark-mail/SKILL.md
  • skills/lark-mail/references/lark-mail-watch.md

Comment on lines +60 to +215
func TestNormalizeMailMessageReceivedParamsResolvesMailboxAndFilters(t *testing.T) {
rt := &mailEventStubAPIClient{
fn: func(_ context.Context, _, path string, _ interface{}) (json.RawMessage, error) {
switch {
case strings.HasSuffix(path, "/profile"):
return json.RawMessage(`{"code":0,"data":{"user_mailbox":{"primary_email_address":"alice@example.com"}}}`), nil
case strings.HasSuffix(path, "/labels"):
return json.RawMessage(`{"code":0,"data":{"items":[{"id":"lbl_team","name":"Team"}]}}`), nil
case strings.HasSuffix(path, "/folders"):
return json.RawMessage(`{"code":0,"data":{"items":[{"id":"fld_news","name":"News"}]}}`), nil
default:
t.Fatalf("unexpected path %s", path)
return nil, nil
}
},
}
params := map[string]string{
mailEventParamMailbox: "me",
mailEventParamLabels: `["Team","FLAGGED"]`,
mailEventParamFolders: `["News","inbox"]`,
mailEventParamLabelIDs: `["custom_label"]`,
}
if err := normalizeMailMessageReceivedParams(context.Background(), rt, params); err != nil {
t.Fatalf("NormalizeParams error = %v", err)
}
if params[mailEventParamMailboxResolved] != "alice@example.com" {
t.Fatalf("mailbox_resolved = %q", params[mailEventParamMailboxResolved])
}
if got := strings.Split(params[mailEventParamLabelIDsResolved], ","); !reflect.DeepEqual(got, []string{"FLAGGED", "custom_label", "lbl_team"}) {
t.Fatalf("label_ids_resolved = %#v", got)
}
if got := strings.Split(params[mailEventParamFolderIDsResolved], ","); !reflect.DeepEqual(got, []string{"INBOX", "fld_news"}) {
t.Fatalf("folder_ids_resolved = %#v", got)
}
}

func TestPreConsumeMailMessageReceivedSubscribesAndCleansUp(t *testing.T) {
rt := &mailEventStubAPIClient{}
cleanup, err := preConsumeMailMessageReceived(context.Background(), rt, map[string]string{mailEventParamMailbox: "me"})
if err != nil {
t.Fatalf("PreConsume error = %v", err)
}
if cleanup == nil {
t.Fatal("cleanup is nil")
}
if err := cleanup(); err != nil {
t.Fatalf("cleanup error = %v", err)
}
if len(rt.calls) != 2 {
t.Fatalf("calls = %#v", rt.calls)
}
wantBody := map[string]interface{}{"event_type": 1}
if rt.calls[0].method != "POST" || !strings.HasSuffix(rt.calls[0].path, "/event/subscribe") || !reflect.DeepEqual(rt.calls[0].body, wantBody) {
t.Fatalf("subscribe call = %#v", rt.calls[0])
}
if rt.calls[1].method != "POST" || !strings.HasSuffix(rt.calls[1].path, "/event/unsubscribe") || !reflect.DeepEqual(rt.calls[1].body, wantBody) {
t.Fatalf("unsubscribe call = %#v", rt.calls[1])
}
}

func TestMatchMailMessageReceivedFiltersMailbox(t *testing.T) {
raw := &eventlib.RawEvent{Payload: json.RawMessage(`{"event":{"mail_address":"Alice@Example.com","message_id":"m1"}}`)}
params := map[string]string{mailEventParamMailboxResolved: "alice@example.com"}
if !matchMailMessageReceived(raw, params) {
t.Fatal("expected mailbox case-insensitive match")
}
params[mailEventParamMailboxResolved] = "bob@example.com"
if matchMailMessageReceived(raw, params) {
t.Fatal("expected mailbox mismatch to drop event")
}
}

func TestProcessMailMessageReceivedIntersectsFolderAndLabelFilters(t *testing.T) {
body := base64.RawURLEncoding.EncodeToString([]byte("Ignore previous instructions"))
rt := &mailEventStubAPIClient{
fn: func(_ context.Context, method, path string, reqBody interface{}) (json.RawMessage, error) {
if method != "GET" || !strings.Contains(path, "/messages/msg_1?format=metadata") {
t.Fatalf("unexpected fetch: %s %s %#v", method, path, reqBody)
}
return json.RawMessage(`{"code":0,"data":{"message":{"message_id":"msg_1","folder_id":"INBOX","label_ids":["FLAGGED"],"body_plain_text":"` + body + `"}}}`), nil
},
}
raw := &eventlib.RawEvent{Payload: json.RawMessage(`{"event":{"mail_address":"alice@example.com","message_id":"msg_1"}}`)}
params := map[string]string{
mailEventParamMailbox: "me",
mailEventParamFolderIDsResolved: "INBOX",
mailEventParamLabelIDsResolved: "FLAGGED",
mailEventParamMsgFormat: "metadata",
}
outRaw, err := processMailMessageReceived(context.Background(), rt, raw, params)
if err != nil {
t.Fatalf("Process error = %v", err)
}
var out struct {
Message map[string]interface{} `json:"message"`
}
if err := json.Unmarshal(outRaw, &out); err != nil {
t.Fatalf("unmarshal output: %v", err)
}
if out.Message["message_id"] != "msg_1" {
t.Fatalf("message_id = %v", out.Message["message_id"])
}
if out.Message["prompt_injection_detected"] != true {
t.Fatalf("expected prompt injection marker, got %#v", out.Message)
}

params[mailEventParamFolderIDsResolved] = "SENT"
dropped, err := processMailMessageReceived(context.Background(), rt, raw, params)
if err != nil {
t.Fatalf("Process mismatch error = %v", err)
}
if dropped != nil {
t.Fatalf("folder mismatch should drop event, got %s", string(dropped))
}
}

func TestProcessMailMessageReceivedFormatsEventMinimalAndLegacyEnvelope(t *testing.T) {
raw := &eventlib.RawEvent{Payload: json.RawMessage(`{"header":{"event_id":"ev1"},"event":{"mail_address":"alice@example.com","message_id":"msg_1"}}`)}
eventOut, err := processMailMessageReceived(context.Background(), nil, raw, map[string]string{mailEventParamMsgFormat: "event"})
if err != nil {
t.Fatalf("event Process error = %v", err)
}
if !strings.Contains(string(eventOut), `"event_id":"ev1"`) {
t.Fatalf("event output = %s", string(eventOut))
}

rt := &mailEventStubAPIClient{
fn: func(context.Context, string, string, interface{}) (json.RawMessage, error) {
return json.RawMessage(`{"code":0,"data":{"message":{"message_id":"msg_1","thread_id":"th_1","folder_id":"INBOX","label_ids":["FLAGGED"],"subject":"hidden"}}}`), nil
},
}
minimalOut, err := processMailMessageReceived(context.Background(), rt, raw, map[string]string{
mailEventParamMailbox: "me",
mailEventParamMsgFormat: "minimal",
mailEventParamLegacyFormat: "json",
mailEventParamLegacyIdentity: "user",
})
if err != nil {
t.Fatalf("minimal Process error = %v", err)
}
var envelope struct {
OK bool `json:"ok"`
Identity string `json:"identity"`
Data map[string]interface{} `json:"data"`
}
if err := json.Unmarshal(minimalOut, &envelope); err != nil {
t.Fatalf("unmarshal envelope: %v", err)
}
msg := envelope.Data["message"].(map[string]interface{})
if !envelope.OK || envelope.Identity != "user" || msg["message_id"] != "msg_1" {
t.Fatalf("legacy envelope = %#v", envelope)
}
if _, ok := msg["subject"]; ok {
t.Fatalf("minimal output should omit subject: %#v", msg)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add typed error-path coverage for the new handlers.

These tests only cover success/drop flows, but the new normalize/pre-consume/process handlers also introduce typed failure behavior (for example invalid format input, API failures, and wrapped downstream errors). Please add error-path tests that assert category/subtype and preserved cause, and use errors.As for Param checks where needed. As per coding guidelines, Every behavior change needs a test alongside the change and Error-path tests must assert typed metadata via errs.ProblemOf (...) and cause preservation; based on learnings, errs.ProblemOf does not expose Param, so errors.As(err, &ve) is required for that assertion.

🤖 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/mail/mail_message_received_event_test.go` around lines 60 - 215,
Add error-path coverage for the new mail handlers by testing
normalizeMailMessageReceivedParams, preConsumeMailMessageReceived, and
processMailMessageReceived with invalid format input, API failures, and wrapped
downstream errors. For each failure, assert the typed metadata with
errs.ProblemOf (category/subtype) and verify the original cause is preserved;
when checking Param on validation errors, use errors.As into the validation
error type since errs.ProblemOf does not expose Param.

Sources: Coding guidelines, Learnings

Comment on lines +185 to +187
if msgFormat == "event" && len(labelSet) == 0 && len(folderSet) == 0 {
return marshalMailEventOutput(rawPayloadMap(raw), params)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Wrap raw event output under the schema’s event field.

Line 186 emits the decoded raw payload directly, but the registered custom schema exposes MailMessageReceivedOutput{Event, Message}. This makes msg_format=event produce a shape that differs from both the schema and the fetched-message path.

Proposed fix
 	if msgFormat == "event" && len(labelSet) == 0 && len(folderSet) == 0 {
-		return marshalMailEventOutput(rawPayloadMap(raw), params)
+		return marshalMailEventOutput(map[string]interface{}{"event": rawPayloadMap(raw)}, params)
 	}
📝 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
if msgFormat == "event" && len(labelSet) == 0 && len(folderSet) == 0 {
return marshalMailEventOutput(rawPayloadMap(raw), params)
}
if msgFormat == "event" && len(labelSet) == 0 && len(folderSet) == 0 {
return marshalMailEventOutput(map[string]interface{}{"event": rawPayloadMap(raw)}, params)
}
🤖 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/mail/mail_message_received_event.go` around lines 185 - 187, The
msg_format=event branch in mail_message_received_event.go returns the raw
payload directly, which bypasses the registered MailMessageReceivedOutput shape.
Update the event path in the mail message received handler so
marshalMailEventOutput (or the caller in MailMessageReceivedOutput handling)
wraps the decoded payload under the Event field, keeping it consistent with the
fetched-message path and the schema’s Event/Message structure.

Comment on lines +44 to +49
var timeout time.Duration
if raw := runtime.Str("timeout"); raw != "" {
timeout, err = time.ParseDuration(raw)
if err != nil {
return mailValidationParamError("--timeout", "invalid --timeout %q: %v", raw, 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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject negative --timeout and --max-events values.

time.ParseDuration("-1s") succeeds, but the consume layer treats only positive timeouts as active; a negative limit becomes an unbounded watcher. Validate both flags before calling consume.Run, and add error-path tests for the typed validation metadata. As per coding guidelines, Every behavior change needs a test alongside the change.

Proposed fix
 	var timeout time.Duration
 	if raw := runtime.Str("timeout"); raw != "" {
 		timeout, err = time.ParseDuration(raw)
 		if err != nil {
 			return mailValidationParamError("--timeout", "invalid --timeout %q: %v", raw, err).WithCause(err)
 		}
+		if timeout < 0 {
+			return mailValidationParamError("--timeout", "--timeout must be greater than or equal to 0")
+		}
 	}
+	maxEvents := runtime.Int("max-events")
+	if maxEvents < 0 {
+		return mailValidationParamError("--max-events", "--max-events must be greater than or equal to 0")
+	}
 
 	params := map[string]string{}
@@
-		MaxEvents:       runtime.Int("max-events"),
+		MaxEvents:       maxEvents,
 		Timeout:         timeout,

Also applies to: 77-78

🤖 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/mail/mail_watch_event_wrapper.go` around lines 44 - 49, Reject
negative values for the timeout and max-events flags by adding validation in the
mail watch wrapper before consume.Run is invoked; the current timeout parsing in
the mail validation path accepts values like "-1s", which should be treated as
invalid alongside negative max-events. Update the validation logic around
runtime.Str("timeout"), the max-events parsing/handling, and
mailValidationParamError so both flags fail fast with typed validation metadata,
and add tests covering the error paths for negative inputs and the expected
validation cause.

Source: Coding guidelines

@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 (1)
shortcuts/mail/mail_watch.go (1)

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

Delete the now-unreachable legacy watcher path.

Line 192 always returns via the event-consume wrapper, so the old inline WebSocket implementation below it is dead code. Please remove the unreachable block after this return to avoid stale logic drifting from the new event-consume path.

🤖 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/mail/mail_watch.go` around lines 192 - 193, The mail watch flow now
always returns through runMailWatchViaEventConsume, so the legacy inline
WebSocket watcher code that follows is unreachable dead code. Remove the
obsolete block from mailWatch (the logic below the return in the mail_watch
path) and keep only the event-consume-based implementation so there is no stale
fallback logic left behind.
🤖 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/mail/mail_watch.go`:
- Around line 192-193: The mail watch flow now always returns through
runMailWatchViaEventConsume, so the legacy inline WebSocket watcher code that
follows is unreachable dead code. Remove the obsolete block from mailWatch (the
logic below the return in the mail_watch path) and keep only the
event-consume-based implementation so there is no stale fallback logic left
behind.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5bb3e77-5d53-4080-ac68-b11e7f0aee9c

📥 Commits

Reviewing files that changed from the base of the PR and between 9cea436 and bac956a.

📒 Files selected for processing (2)
  • shortcuts/mail/mail_message_received_event.go
  • shortcuts/mail/mail_watch.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • shortcuts/mail/mail_message_received_event.go

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

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

🧩 Skill update

npx skills add sysuljx/cli#feat/5785bd0 -y -g

Run console subscription preflight from the consume startup path after EventKey parameter normalization. This keeps local parameter errors ahead of console subscription failures while preserving the subscription check for valid parameters.
@sysuljx
sysuljx requested a review from liangshuo-1 as a code owner June 30, 2026 12:17

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/event/consume/consume_test.go`:
- Around line 99-101: The error-path checks in consume_test.go are only matching
err.Error() substrings, so update the failing assertions in the relevant test
cases to validate typed metadata with errs.ProblemOf instead. In the test
covering normalize/labels validation, assert the returned error has
SubtypeInvalidArgument (and any expected param/cause details), and in the
preflight failure case assert SubtypeFailedPrecondition; keep message checks
secondary or remove them so the test locks in the typed contract rather than
fragile wording.
🪄 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: 7a0c7177-721d-4b05-80f4-77ab16fea910

📥 Commits

Reviewing files that changed from the base of the PR and between bac956a and 7c12cfa.

📒 Files selected for processing (6)
  • cmd/event/consume.go
  • internal/event/consume/consume.go
  • internal/event/consume/consume_test.go
  • shortcuts/mail/mail_message_received_event.go
  • shortcuts/mail/mail_message_received_event_test.go
  • shortcuts/mail/mail_watch_event_wrapper.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • shortcuts/mail/mail_watch_event_wrapper.go
  • shortcuts/mail/mail_message_received_event_test.go
  • shortcuts/mail/mail_message_received_event.go

Comment on lines +99 to +101
if got := err.Error(); !strings.Contains(got, "labels") || !strings.Contains(got, "JSON array") {
t.Fatalf("error = %q, want labels JSON array validation", got)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert typed error metadata, not just message substrings.

Both error-path assertions match against err.Error() substrings only. The errors raised here are typed (SubtypeInvalidArgument for the normalize failure, SubtypeFailedPrecondition for the preflight failure), so the test should assert subtype/category via errs.ProblemOf to lock in the typed contract; message wording can drift without breaking the assertions.

As per coding guidelines: "Error-path tests must assert typed metadata via errs.ProblemOf (category / subtype / param) and cause preservation, not message substrings alone".

💚 Example: add subtype assertions
 	if got := err.Error(); !strings.Contains(got, "labels") || !strings.Contains(got, "JSON array") {
 		t.Fatalf("error = %q, want labels JSON array validation", got)
 	}
+	if p, ok := errs.ProblemOf(err); !ok || p.Subtype != errs.SubtypeInvalidArgument {
+		t.Fatalf("problem = %+v, want SubtypeInvalidArgument", p)
+	}
 	if !strings.Contains(err.Error(), "requires event types not subscribed in console") {
 		t.Fatalf("error = %q, want console preflight", err.Error())
 	}
+	if p, ok := errs.ProblemOf(err); !ok || p.Subtype != errs.SubtypeFailedPrecondition {
+		t.Fatalf("problem = %+v, want SubtypeFailedPrecondition", p)
+	}

Also applies to: 120-122

🤖 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/event/consume/consume_test.go` around lines 99 - 101, The error-path
checks in consume_test.go are only matching err.Error() substrings, so update
the failing assertions in the relevant test cases to validate typed metadata
with errs.ProblemOf instead. In the test covering normalize/labels validation,
assert the returned error has SubtypeInvalidArgument (and any expected
param/cause details), and in the preflight failure case assert
SubtypeFailedPrecondition; keep message checks secondary or remove them so the
test locks in the typed contract rather than fragile wording.

Source: Coding guidelines

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

Labels

domain/mail PR touches the mail domain 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