* feat(cli): refine per-status error copy with actionable hints (PR2)
Builds on PR1's translation layer. Each HTTP-status message now carries an
actionable next step, in both English and Chinese:
- 401: run `multica login`; plus a self-hosted / non-OAuth fallback telling
the user to ask their administrator for valid credentials
- 403: check the workspace / ask an admin to grant access
- 404: check the ID or run the matching `list` command
- 409: re-fetch the latest state and retry
- 422: check values / run with --help
- 429: wait and retry; reduce call frequency if it persists
- 5xx: retry, contact support, and re-run with --debug for the raw response
Also adds ErrorKind.String() (stable snake_case identifiers) and uses it in
--debug output instead of the raw int, and clears the pre-existing gofmt dirt
Eve flagged in cmd_config.go, cmd_version.go, and help.go.
Tests: TestErrorKindString (all kinds + uniqueness + out-of-range fallback)
and TestFormatErrorActionableHints (locks the per-status hints in EN and ZH).
Refs MUL-3104. PR2 of 3.
Co-authored-by: multica-agent <github@multica.ai>
* test(cli): cover validation (400/422) actionable hint
TestFormatErrorActionableHints omitted KindValidation, so deleting the 400/422
hint would have gone unnoticed. Add 400 and 422 cases (no server message, so
the generic validation copy is used) asserting EN contains --help / expected
format and ZH contains --help / 格式 / 参数.
Refs MUL-3104, PR #3897.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>
* feat(cli): add central error translation layer (PR1)
Introduce server/internal/cli/errors.go, a single user-facing error
translation layer that collapses raw transport errors, HTTP status
errors, and internal verb-wrapped chains into clear, localized messages.
- ErrorKind classification (network timeout/DNS/refused/TLS/offline,
401/403/404/409/400+422/429/5xx, unknown)
- NetworkError wraps transport errors and strips the raw URL from the
user-facing message; classifyNetworkError categorizes via errors.As/Is
with string fallbacks
- HTTPError.Kind() maps status codes onto ErrorKind
- FormatError: bilingual output (English default, auto-switch to Chinese
on a zh LC_ALL/LC_MESSAGES/LANG locale), validation errors surface the
server message; --debug / MULTICA_DEBUG appends the full raw chain
- ExitCodeFor: tiered exit codes (network=2, auth=3, 404=4, validation=5,
other=1)
- client.go: default HTTP timeout 15s -> 30s, overridable via
MULTICA_HTTP_TIMEOUT; wrap every transport Do() error as *NetworkError
- main.go: route errors through FormatError + ExitCodeFor, add persistent
--debug flag
Unit tests cover every ErrorKind, classification, language detection,
exit codes, server-message extraction, and timeout parsing.
Refs MUL-3104. PR1 of 3; PR2/PR3 (status-code copy refinement and
per-command customization) follow separately.
Co-authored-by: multica-agent <github@multica.ai>
* fix(cli): address review — unify command timeouts and classify all helper errors
Must-fix 1: command-level contexts no longer truncate MULTICA_HTTP_TIMEOUT.
Added cli.APITimeout/AtLeastAPITimeout/APIContext (budget = transport timeout
+ small grace, honoring MULTICA_HTTP_TIMEOUT) and replaced the hardcoded 15s
context.WithTimeout in every API command (14 files, 92 sites) with
cli.APIContext. The issue-create/comment path now uses APITimeout() with a
60s floor for attachment uploads.
Must-fix 2: all API helpers now return *HTTPError on status >= 400. Added a
shared newHTTPError(method, path, resp) and routed GetJSON, GetJSONWithHeaders,
PostJSON, PutJSON, PatchJSON, DeleteJSON, DeleteJSONWithBody, UploadFile,
UploadFileWithURL, DownloadFile (and HealthCheck) through it, so issue
update/status/metadata (PUT), comment list (GetJSONWithHeaders), project/label/
comment delete (DELETE) and agent/workspace/autopilot update (PUT/PATCH) all
get HTTPError.Kind() classification, friendly copy, and the tiered exit code
instead of the raw string + exit 1.
Tests: new errors_integration_test.go drives the real helpers against a fake
server and asserts FormatError copy + ExitCodeFor for 401/403/404/422/500
across all 10 helpers, plus a slow-server test proving the command context
does not cancel before the transport timeout. Updated the UploadFileWithURL
assertion to check for *HTTPError.
Refs MUL-3104, PR #3892.
Co-authored-by: multica-agent <github@multica.ai>
* fix(cli): make remaining fixed-timeout API commands honor MULTICA_HTTP_TIMEOUT
Closes out the timeout work: the last API command paths still used a
hardcoded context deadline that capped MULTICA_HTTP_TIMEOUT. Converted them
to cli.AtLeastAPITimeout(<original floor>) so the env override scales them up
while preserving each original lower bound:
- cmd_autopilot.go autopilot trigger 30s -> AtLeastAPITimeout(30s)
- cmd_attachment.go attachment download 60s -> AtLeastAPITimeout(60s)
- cmd_agent.go avatar upload 60s -> AtLeastAPITimeout(60s)
- cmd_skill.go skill import / search 60s -> AtLeastAPITimeout(60s)
- cmd_runtime.go runtime update 150s -> AtLeastAPITimeout(150s)
- cmd_login.go workspace-creation poll 10s -> AtLeastAPITimeout(10s)
The login poll keeps a short 10s floor to stay responsive within its 5-minute
loop, but it is NOT a silent exception: AtLeastAPITimeout means it still scales
with MULTICA_HTTP_TIMEOUT. Documented in code and covered by a new subtest in
TestAPITimeoutRespectsEnv.
Refs MUL-3104, PR #3892.
Co-authored-by: multica-agent <github@multica.ai>
* style(cli): gofmt cmd_attachment.go to unblock backend CI
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: multica-agent <github@multica.ai>