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:
WJzz1
2026-05-25 12:08:18 +08:00
committed by GitHub
parent 708cbc2b31
commit 9d4233bfe3
2 changed files with 34 additions and 1 deletions

View File

@@ -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
}

View File

@@ -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.