fix: migrate task shortcut errors from bare fmt.Errorf to structured …#740
Conversation
📝 WalkthroughWalkthroughStandardizes error handling in the task shortcuts: input/parse failures now return structured validation exit errors ( ChangesTask shortcuts — validation & API error standardization
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
Pull request overview
This PR migrates error returns in the task shortcuts (shortcuts/task/) from bare fmt.Errorf to structured internal/output errors (output.Errorf / output.ErrValidation) to preserve the CLI’s JSON error envelope contract for AI agent consumers.
Changes:
- Replaced validation errors with
output.ErrValidationin task body/time parsing helpers. - Replaced API response unmarshal errors with
output.Errorf(output.ExitAPI, "api_error", ...). - Added/updated tests to assert returned errors are
*output.ExitErrorwith the expected exit codes and detail types.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| shortcuts/task/task_util.go | Converts parseRelativeTime failures to structured validation errors. |
| shortcuts/task/shortcuts.go | Converts create-body validation errors + create response parse errors to structured output errors. |
| shortcuts/task/task_update.go | Converts update-body validation errors + update response parse errors to structured output errors. |
| shortcuts/task/task_query_helpers.go | Converts time-range parsing validation failures to structured validation errors. |
| shortcuts/task/task_util_test.go | Adds structured-error assertions for parseRelativeTime. |
| shortcuts/task/task_body_test.go | Adds new structured-error tests for create/update body builders. |
| shortcuts/task/task_query_helpers_test.go | Adds structured-error assertions for reversed time-range cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
shortcuts/task/task_query_helpers_test.go (1)
257-286:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winMirror the structured validation assertion here.
The RFC3339 case only checks the exit code. Please also assert
Detail.Type == "validation"like the millis case so this path is covered by the same contract.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@shortcuts/task/task_query_helpers_test.go` around lines 257 - 286, In the parseTimeRangeRFC3339 test, extend the validation for the "reversed range fails fast" case to mirror the millis-case assertions: after asserting the error is an *output.ExitError and that exitErr.Code == output.ExitValidation (within the t.Run for parseTimeRangeRFC3339), also assert exitErr.Detail.Type == "validation" so the RFC3339 path enforces the same contract; reference parseTimeRangeRFC3339, output.ExitError, output.ExitValidation, and exitErr.Detail.Type when adding this assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@shortcuts/task/task_body_test.go`:
- Around line 16-76: Add assertions in TestBuildTaskCreateBody_StructuredErrors
to verify the error message contains the expected substring (tt.wantSubstr).
After you obtain exitErr from buildTaskCreateBody, assert that
exitErr.Detail.Message (or exitErr.Error() if Message is empty) contains
tt.wantSubstr using strings.Contains; reference the test name
TestBuildTaskCreateBody_StructuredErrors, the table field wantSubstr, and the
error variable exitErr to locate where to add the check so the structured-error
tests fail on message regressions.
In `@shortcuts/task/task_util_test.go`:
- Around line 23-62: The test TestParseRelativeTime_StructuredErrors defines
wantSubstr but never asserts the human-readable message; update the test to
assert that exitErr.Detail.Message (the error text on the structured detail)
contains tt.wantSubstr using strings.Contains and t.Errorf when it does not, so
the validation message is checked for the expected substring after the existing
non-nil and Type assertions around parseRelativeTime.
---
Outside diff comments:
In `@shortcuts/task/task_query_helpers_test.go`:
- Around line 257-286: In the parseTimeRangeRFC3339 test, extend the validation
for the "reversed range fails fast" case to mirror the millis-case assertions:
after asserting the error is an *output.ExitError and that exitErr.Code ==
output.ExitValidation (within the t.Run for parseTimeRangeRFC3339), also assert
exitErr.Detail.Type == "validation" so the RFC3339 path enforces the same
contract; reference parseTimeRangeRFC3339, output.ExitError,
output.ExitValidation, and exitErr.Detail.Type when adding this assertion.
🪄 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: 2819e420-0d47-4916-ba99-fe6033c41dbd
📒 Files selected for processing (7)
shortcuts/task/shortcuts.goshortcuts/task/task_body_test.goshortcuts/task/task_query_helpers.goshortcuts/task/task_query_helpers_test.goshortcuts/task/task_update.goshortcuts/task/task_util.goshortcuts/task/task_util_test.go
07765cd to
0f3be77
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@shortcuts/task/task_update.go`:
- Around line 134-137: After unmarshalling dataStr into taskObj using
json.Unmarshal in the block that checks runtime.Str("data"), add a nil-check for
taskObj and either initialize it to an empty map (e.g., taskObj =
make(map[string]interface{})) or return a validation error if the caller passed
JSON null; this prevents panics when later code assigns taskObj["summary"],
taskObj["description"], and taskObj["due"]. Locate the json.Unmarshal call and
runtime.Str("data") check and ensure taskObj is non-nil before any field
assignments, preserving the existing output.ErrValidation error behavior on
malformed JSON.
🪄 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: c87f24c0-be2a-44a6-820a-f82f562fb80f
📒 Files selected for processing (7)
shortcuts/task/shortcuts.goshortcuts/task/task_body_test.goshortcuts/task/task_query_helpers.goshortcuts/task/task_query_helpers_test.goshortcuts/task/task_update.goshortcuts/task/task_util.goshortcuts/task/task_util_test.go
✅ Files skipped from review due to trivial changes (2)
- shortcuts/task/task_util_test.go
- shortcuts/task/task_query_helpers.go
🚧 Files skipped from review as they are similar to previous changes (3)
- shortcuts/task/task_util.go
- shortcuts/task/task_body_test.go
- shortcuts/task/task_query_helpers_test.go
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (42.85%) is below the target coverage (60.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #740 +/- ##
==========================================
+ Coverage 64.96% 65.14% +0.18%
==========================================
Files 502 503 +1
Lines 46224 46576 +352
==========================================
+ Hits 30030 30344 +314
- Misses 13583 13592 +9
- Partials 2611 2640 +29 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
🚀 PR Preview Install Guide🧰 CLI updatenpm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@8221661091fa8159d405524254b0ea16e15ee21d🧩 Skill updatenpx skills add Zhang-986/cli#fix/task-structured-errors -y -g |
…output.Errorf/ErrValidation
0f3be77 to
8221661
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
shortcuts/task/shortcuts.go (2)
100-109:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
--data nullcauses a nil-map panic inbuildTaskCreateBody.
json.Unmarshal([]byte("null"), &body)returnsnilerror but setsbodytonil. Line 102 only guards the error path; the success path with JSONnullbypasses it, leavingbody == nil. The assignmentbody["summary"] = summaryat Line 108 then panics at runtime.Same root cause as the flagged issue in
buildTaskUpdateBody.🐛 Proposed fix
if dataStr := runtime.Str("data"); dataStr != "" { if err := json.Unmarshal([]byte(dataStr), &body); err != nil { return nil, output.ErrValidation("--data must be a valid JSON object: %v", err) } + if body == nil { + return nil, output.ErrValidation("--data must be a non-null JSON object") + } }🤖 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/task/shortcuts.go` around lines 100 - 109, buildTaskCreateBody can receive JSON "null" which json.Unmarshal([]byte(dataStr), &body) accepts but leaves body == nil, causing a panic when later assigning body["summary"]; update buildTaskCreateBody (and mirror the same pattern used in buildTaskUpdateBody) to check if body == nil after unmarshalling and if so initialize it with body = make(map[string]interface{}) (or return a validation error if you prefer to forbid null), ensuring subsequent assignments like body["summary"] are safe.
186-190:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidation errors from
buildTaskCreateBodyare double-wrapped viaWrapTaskError.Line 189 passes
buildTaskCreateBody's*output.ExitErrorintoWrapTaskError(ErrCodeTaskInvalidParams, err.Error(), "create task"), which produces a secondExitError. The structured detail (type, hint, code) from the originalErrValidationis discarded and replaced by the generic task-params entry intaskErrorMap. This inconsistency doesn't occur inUpdateTask.Execute(Line 52–55 oftask_update.go) — update returns the builder's error directly.Verify whether
CreateTask.Executeshould propagate the builder error directly, asUpdateTaskalready does.🛠️ Proposed fix
body, err := buildTaskCreateBody(runtime) if err != nil { - return WrapTaskError(ErrCodeTaskInvalidParams, err.Error(), "create task") + 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/task/shortcuts.go` around lines 186 - 190, The Create task Execute handler is double-wrapping validation errors from buildTaskCreateBody by calling WrapTaskError(ErrCodeTaskInvalidParams, err.Error(), "create task"), which discards the original *output.ExitError structured details; change Execute in the create task (the Execute func that calls buildTaskCreateBody) to return the builder error directly (i.e., return err) instead of wrapping it so structured validation errors from buildTaskCreateBody propagate (do not call WrapTaskError for errors returned from buildTaskCreateBody); keep WrapTaskError(ErrCodeTaskInvalidParams, ...) only for genuine new wrapping cases where you are creating a new task-level error.
♻️ Duplicate comments (2)
shortcuts/task/task_util_test.go (1)
23-62:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
wantSubstris still defined but never asserted.The field
wantSubstr(Line 29) and its value"invalid relative time format"(Line 36) are populated but the assertion loop never checkserr.Error()orexitErr.Detail.Messageagainst it, leaving a message-regression gap.🛠️ Proposed fix
import ( "errors" + "strings" "testing"if exitErr.Detail.Type != tt.wantType { t.Errorf("error type = %q, want %q", exitErr.Detail.Type, tt.wantType) } + if tt.wantSubstr != "" && !strings.Contains(exitErr.Detail.Message, tt.wantSubstr) { + t.Errorf("error message = %q, want substring %q", exitErr.Detail.Message, tt.wantSubstr) + } })🤖 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/task/task_util_test.go` around lines 23 - 62, The test TestParseRelativeTime_StructuredErrors populates wantSubstr but never asserts it; update the table-driven test loop to assert that the returned error message contains tt.wantSubstr by checking either exitErr.Detail.Message (if non-nil) or err.Error() as a fallback after calling parseRelativeTime, and fail the test with t.Errorf when the substring is not found; reference parseRelativeTime and exitErr.Detail.Message in your assertion so the test covers the expected message regression.shortcuts/task/task_update.go (1)
134-141:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
--data nullstill panics: nil map write at Line 145.
json.Unmarshal([]byte("null"), &taskObj)silently setstaskObjtonil(valid JSON, no error returned). Therange taskObjon Line 139 is safe (nil-map range is a no-op), but the subsequenttaskObj["summary"] = summaryat Line 145 panics on a nil map.🐛 Proposed fix
if dataStr := runtime.Str("data"); dataStr != "" { if err := json.Unmarshal([]byte(dataStr), &taskObj); err != nil { return nil, output.ErrValidation("--data must be a valid JSON object: %v", err) } + if taskObj == nil { + return nil, output.ErrValidation("--data must be a non-null JSON object") + } for k := range taskObj {🤖 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/task/task_update.go` around lines 134 - 141, The code unmarshals runtime.Str("data") into taskObj which can become a nil map when the JSON is "null", causing a panic when later writing taskObj["summary"] in the update flow; after calling json.Unmarshal in the block that reads runtime.Str("data"), check if taskObj == nil and if so initialize it to an empty map (e.g., make(map[string]interface{})) before iterating and before any writes, and then continue building updateFields and assigning summary—refer to taskObj, updateFields, json.Unmarshal and runtime.Str in task_update.go to locate the changes.
🤖 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/task/task_query_helpers_test.go`:
- Around line 278-286: The reversed-range test branch inside the
parseTimeRangeRFC3339 test is missing the assertion that the ExitError detail
type equals "validation"; update the block handling tt.name == "reversed range
fails fast" in shortcuts/task/task_query_helpers_test.go to mirror
parseTimeRangeMillis by asserting exitErr.Detail.Type == "validation" (in
addition to the existing errors.As and exitErr.Code checks) so the error detail
type is validated for parseTimeRangeRFC3339 as well.
---
Outside diff comments:
In `@shortcuts/task/shortcuts.go`:
- Around line 100-109: buildTaskCreateBody can receive JSON "null" which
json.Unmarshal([]byte(dataStr), &body) accepts but leaves body == nil, causing a
panic when later assigning body["summary"]; update buildTaskCreateBody (and
mirror the same pattern used in buildTaskUpdateBody) to check if body == nil
after unmarshalling and if so initialize it with body =
make(map[string]interface{}) (or return a validation error if you prefer to
forbid null), ensuring subsequent assignments like body["summary"] are safe.
- Around line 186-190: The Create task Execute handler is double-wrapping
validation errors from buildTaskCreateBody by calling
WrapTaskError(ErrCodeTaskInvalidParams, err.Error(), "create task"), which
discards the original *output.ExitError structured details; change Execute in
the create task (the Execute func that calls buildTaskCreateBody) to return the
builder error directly (i.e., return err) instead of wrapping it so structured
validation errors from buildTaskCreateBody propagate (do not call WrapTaskError
for errors returned from buildTaskCreateBody); keep
WrapTaskError(ErrCodeTaskInvalidParams, ...) only for genuine new wrapping cases
where you are creating a new task-level error.
---
Duplicate comments:
In `@shortcuts/task/task_update.go`:
- Around line 134-141: The code unmarshals runtime.Str("data") into taskObj
which can become a nil map when the JSON is "null", causing a panic when later
writing taskObj["summary"] in the update flow; after calling json.Unmarshal in
the block that reads runtime.Str("data"), check if taskObj == nil and if so
initialize it to an empty map (e.g., make(map[string]interface{})) before
iterating and before any writes, and then continue building updateFields and
assigning summary—refer to taskObj, updateFields, json.Unmarshal and runtime.Str
in task_update.go to locate the changes.
In `@shortcuts/task/task_util_test.go`:
- Around line 23-62: The test TestParseRelativeTime_StructuredErrors populates
wantSubstr but never asserts it; update the table-driven test loop to assert
that the returned error message contains tt.wantSubstr by checking either
exitErr.Detail.Message (if non-nil) or err.Error() as a fallback after calling
parseRelativeTime, and fail the test with t.Errorf when the substring is not
found; reference parseRelativeTime and exitErr.Detail.Message in your assertion
so the test covers the expected message regression.
🪄 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: 52ed80b1-c2cf-4d8c-812f-39b0dc6d2abe
📒 Files selected for processing (7)
shortcuts/task/shortcuts.goshortcuts/task/task_body_test.goshortcuts/task/task_query_helpers.goshortcuts/task/task_query_helpers_test.goshortcuts/task/task_update.goshortcuts/task/task_util.goshortcuts/task/task_util_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- shortcuts/task/task_body_test.go
…output.Errorf/ErrValidation (larksuite#740)
Summary
Migrated 16 error paths in the task management shortcuts (
shortcuts/task/) from barefmt.Errorfto structuredoutput.Errorf/output.ErrValidation, enabling AI agent consumers (Claude Code, Cursor, Gemini CLI) to programmatically parse and recover from errors via the JSON error envelope contract.Per AGENTS.md: "RunE functions must return output.Errorf / output.ErrWithHint — never bare fmt.Errorf. AI agents parse stderr as JSON; bare errors break this contract."
Changes
Error migration (16 paths across 4 files)
task_util.go(2 paths — validation errors):parseRelativeTime: invalid format →output.ErrValidationparseRelativeTime: unknown unit →output.ErrValidationshortcuts.go(4 paths — 3 validation + 1 API):buildTaskCreateBody: invalid--dataJSON →output.ErrValidationbuildTaskCreateBody: invalid--duetime →output.ErrValidationbuildTaskCreateBody: missing summary →output.ErrValidationCreateTask.Execute: response parse failure →output.Errorf(ExitAPI, "api_error", ...)task_update.go(4 paths — 3 validation + 1 API):UpdateTask.Execute: response parse failure →output.Errorf(ExitAPI, "api_error", ...)buildTaskUpdateBody: invalid--dataJSON →output.ErrValidationbuildTaskUpdateBody: invalid--duetime →output.ErrValidationbuildTaskUpdateBody: no fields to update →output.ErrValidationtask_query_helpers.go(6 paths — all validation):parseTimeRangeMillis: invalid start/end timestamp →output.ErrValidationparseTimeRangeMillis: reversed time range →output.ErrValidationparseTimeRangeRFC3339: invalid start/end timestamp →output.ErrValidationparseTimeRangeRFC3339: reversed time range →output.ErrValidationTest additions
task_util_test.go: AddedTestParseRelativeTime_StructuredErrors— verifies*output.ExitErrortype, exit code, and error detail type.task_body_test.go(new): AddedTestBuildTaskCreateBody_StructuredErrorsandTestBuildTaskUpdateBody_StructuredErrors— validates structured error output for invalid JSON data, missing summary, and no-fields-to-update cases.task_query_helpers_test.go: Enhanced existing tests with*output.ExitErrortype assertions for reversed time range errors.Test plan
go test ./shortcuts/task/... -race— all passgo vet ./shortcuts/task/...— cleangofmt -l shortcuts/task/— no outputgo mod tidy— no changesgolangci-lint run --new-from-rev=origin/main ./shortcuts/task/...— 0 issuesSummary by CodeRabbit
Bug Fixes
Tests