mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
fix(contact): add actionable hint when fanout search all-fail with no API code (#1054)
In buildFanoutResponse, when every fanout query fails AND the first failure has no Lark API code (i.e. transport, parse, panic, or context-cancel), the returned ExitError was carrying an empty Hint. This is the only output.ErrWithHint call in shortcuts/ that ships an empty hint. AGENTS.md states: "every error message you write will be parsed by an AI to decide its next action. Make errors structured, actionable, and specific." An empty hint gives the agent nothing to do. Populate the hint with the actionable next step for this branch — retry, and if it persists, narrow --queries to a single term to isolate the failing input. The companion test exercises the no-code path and asserts the hint is non-empty and mentions "retry". Co-authored-by: Wang-Yeah623 <Wang-Yeah623@users.noreply.github.com>
This commit is contained in:
@@ -176,7 +176,11 @@ func buildFanoutResponse(queries []string, results []fanoutResult) (*fanoutRespo
|
||||
if firstErrCode != 0 {
|
||||
return nil, output.ErrAPI(firstErrCode, msg, "")
|
||||
}
|
||||
return nil, output.ErrWithHint(output.ExitInternal, "fanout", msg, "")
|
||||
// No structured API code — the failure was transport, parse, panic, or
|
||||
// cancellation. Suggest the actionable next step rather than shipping
|
||||
// an empty hint that would leave the calling agent with nothing to do.
|
||||
return nil, output.ErrWithHint(output.ExitInternal, "fanout", msg,
|
||||
"retry the command; if it persists, narrow --queries to a single term to isolate the failing input")
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/httpmock"
|
||||
"github.com/larksuite/cli/internal/output"
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -1133,6 +1135,33 @@ func TestFanoutAssemble_AllFailed_ReturnsError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// When all queries fail with no structured Lark API code (transport, parse,
|
||||
// panic, ctx-canceled), the returned ExitError must carry an actionable
|
||||
// hint so the calling agent has a next step to try instead of giving up.
|
||||
func TestFanoutAssemble_AllFailed_NoCode_HasActionableHint(t *testing.T) {
|
||||
results := []fanoutResult{
|
||||
{Index: 0, Query: "alice", ErrMsg: "transport: connection refused"},
|
||||
{Index: 1, Query: "bob", ErrMsg: "transport: timeout"},
|
||||
}
|
||||
_, err := buildFanoutResponse([]string{"alice", "bob"}, results)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error when all queries failed")
|
||||
}
|
||||
var exitErr *output.ExitError
|
||||
if !errors.As(err, &exitErr) {
|
||||
t.Fatalf("expected *output.ExitError, got %T", err)
|
||||
}
|
||||
if exitErr.Detail == nil {
|
||||
t.Fatalf("expected Detail, got nil")
|
||||
}
|
||||
if exitErr.Detail.Hint == "" {
|
||||
t.Errorf("expected non-empty Hint so agents have a next step; got empty")
|
||||
}
|
||||
if !strings.Contains(exitErr.Detail.Hint, "retry") {
|
||||
t.Errorf("hint should suggest retry as the first action; got %q", exitErr.Detail.Hint)
|
||||
}
|
||||
}
|
||||
|
||||
// Codes from the first failure must propagate through output.ErrAPI so the
|
||||
// CLI's exit-code classifier sees the real signal (e.g., 99991663 rate limit)
|
||||
// instead of 0, which would mean "success" in the Lark protocol.
|
||||
|
||||
Reference in New Issue
Block a user