Skip to content

Commit dc31183

Browse files
Merge pull request #35 from MiniMax-AI-Dev/feat/errors-handler-docs
feat(errors): improve global error interception for filesystem, network and format errors
2 parents bc04614 + c27f9fb commit dc31183

7 files changed

Lines changed: 382 additions & 3 deletions

File tree

ERRORS.md

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
# MiniMax CLI Error Reference
2+
3+
This document lists all error scenarios and the messages users will see.
4+
5+
## Auth Commands
6+
7+
### `minimax auth login`
8+
9+
| Scenario | Error Message |
10+
|---|---|
11+
| `--method api-key` without `--api-key` | `--api-key is required when using --method api-key.` |
12+
| API key validation failed | `API key validation failed.` |
13+
| OAuth callback timeout (120s) | `OAuth callback timed out.` |
14+
| OAuth state mismatch | `Invalid OAuth callback.` |
15+
| OAuth error in callback | `OAuth error: ${error}` |
16+
| OAuth token exchange failed | `OAuth token exchange failed: ${body}` |
17+
| `MINIMAX_API_KEY` already set (non-interactive) | `Warning: MINIMAX_API_KEY is already set in environment.` |
18+
19+
### `minimax auth logout`
20+
21+
| Scenario | Error Message |
22+
|---|---|
23+
| No credentials to clear | `No credentials to clear.` |
24+
25+
### `minimax auth refresh`
26+
27+
| Scenario | Error Message |
28+
|---|---|
29+
| No OAuth credentials (api-key mode) | `Not applicable: not authenticated via OAuth.` |
30+
| Refresh token expired | `OAuth session expired and could not be refreshed.` |
31+
32+
### `minimax auth status`
33+
34+
| Scenario | Error Message |
35+
|---|---|
36+
| No credentials | `authenticated: false` + `Not authenticated.` |
37+
| Quota request failed | `Failed to fetch quota: ${err.message}` |
38+
39+
---
40+
41+
## Text Commands
42+
43+
### `minimax text chat`
44+
45+
| Scenario | Error Message |
46+
|---|---|
47+
| No `--message` in non-interactive mode | `Missing required argument: --message` |
48+
| `--messages-file` file not found | `File not found: ${filePath}` |
49+
| `--messages-file` content is not valid JSON | `--messages-file content is not valid JSON.` |
50+
| `--tool` not valid JSON (not a file path) | `--tool argument "${t}" is not valid JSON.` |
51+
| `--tool` file not found | `Tool definition file not found: ${t}` |
52+
| `--tool` file exists but invalid JSON | `Tool definition file "${t}" contains invalid JSON.` |
53+
| Stream disconnected mid-response | `Stream disconnected before response completed.` |
54+
55+
---
56+
57+
## Image Commands
58+
59+
### `minimax image generate`
60+
61+
| Scenario | Error Message |
62+
|---|---|
63+
| No `--prompt` in non-interactive mode | `Missing required argument: --prompt` |
64+
| `--subject-ref` local image not found | `Subject reference image not found: ${params.image}` |
65+
| `--subject-ref` image unreadable | `Cannot read image file: ${e.message}` |
66+
| `--out-dir` no write permission | `Permission denied: cannot create directory "${outDir}".` |
67+
| `--out-dir` other error | `Cannot create directory "${outDir}": ${e.message}` |
68+
| `success_count === 0` (all rejected) | `Image generation failed: all images were rejected (content policy or model error).` |
69+
70+
---
71+
72+
## Video Commands
73+
74+
### `minimax video generate`
75+
76+
| Scenario | Error Message |
77+
|---|---|
78+
| No `--prompt` in non-interactive mode | `Missing required argument: --prompt` |
79+
| `--first-frame` file not found | `First-frame image not found: ${framePath}` |
80+
| `--first-frame` file unreadable | `Cannot read image file: ${e.message}` |
81+
| Task status `Failed` | `Task Failed: ${status_msg}` (when `base_resp.status_code` is 0 or absent); otherwise [API Errors](#api-errors) apply |
82+
| Task status `Unknown` | `Task Unknown: ${status_msg}` (when `base_resp.status_code` is 0 or absent); otherwise [API Errors](#api-errors) apply |
83+
| Success but no `file_id` | `Task completed but no file_id returned.` |
84+
| `file_id` has no `download_url` | `No download URL available for this file.` |
85+
| Polling timeout | `Polling timed out.` |
86+
| Download network interrupted | `Network request failed.` |
87+
| Disk full | `Disk full — cannot write video file.` |
88+
| `--download` path no write permission | `Cannot write file: ${e.message}` |
89+
90+
### `minimax video task get`
91+
92+
| Scenario | Error Message |
93+
|---|---|
94+
| No `--task-id` | `--task-id is required.` |
95+
96+
### `minimax video download`
97+
98+
| Scenario | Error Message |
99+
|---|---|
100+
| No `--file-id` | `--file-id is required.` |
101+
| No `--out` | `--out is required.` |
102+
| `download_url` is empty | `No download URL available for this file.` |
103+
| Download failed (HTTP error) | `Download failed: HTTP ${res.status}` |
104+
| Disk full | `Disk full — cannot write video file.` |
105+
| Output path no write permission | `Cannot write file: ${e.message}` |
106+
107+
---
108+
109+
## Speech Commands
110+
111+
### `minimax speech synthesize`
112+
113+
| Scenario | Error Message |
114+
|---|---|
115+
| No `--text` and no `--text-file` | `--text or --text-file is required.` |
116+
| `--text-file` not found | `File not found: ${flags.textFile}` |
117+
| `--text-file` unreadable | `Cannot read file: ${e.message}` |
118+
| `--out` path no write permission | `Permission denied: cannot write to "${outPath}".` |
119+
| Disk full | `Disk full — cannot write audio file.` |
120+
121+
### `minimax speech voices`
122+
123+
All errors fall under [Network Errors](#networkerrors).
124+
125+
---
126+
127+
## Music Commands
128+
129+
### `minimax music generate`
130+
131+
| Scenario | Error Message |
132+
|---|---|
133+
| Neither `--prompt` nor `--lyrics` provided | `At least one of --prompt or --lyrics is required.` |
134+
| `--lyrics-file` not found | `File not found: ${flags.lyricsFile}` |
135+
| `--lyrics-file` unreadable | `Cannot read file: ${e.message}` |
136+
| `--out` path no write permission | `Permission denied: cannot write to "${outPath}".` |
137+
| Disk full | `Disk full — cannot write audio file.` |
138+
139+
---
140+
141+
## Vision Commands
142+
143+
### `minimax vision describe`
144+
145+
| Scenario | Error Message |
146+
|---|---|
147+
| Neither `--image` nor `--file-id` in non-interactive mode | `Missing required argument. Must provide either --image or --file-id.` |
148+
| Both `--image` and `--file-id` provided | `Conflicting arguments: cannot provide both --image and --file-id.` |
149+
| Local image file not found | `File not found: ${image}` |
150+
| Image format not supported | `Unsupported image format "${ext}". Supported: jpg, jpeg, png, webp` |
151+
| Remote image URL download failed | `Failed to download image: HTTP ${res.status}` |
152+
153+
---
154+
155+
## Search Commands
156+
157+
### `minimax search query`
158+
159+
| Scenario | Error Message |
160+
|---|---|
161+
| No `--q` in non-interactive mode | `--q is required.` |
162+
163+
---
164+
165+
## Quota Commands
166+
167+
### `minimax quota show`
168+
169+
All errors fall under [Network Errors](#networkerrors).
170+
171+
---
172+
173+
## Config Commands
174+
175+
### `minimax config set`
176+
177+
| Scenario | Error Message |
178+
|---|---|
179+
| `--key` or `--value` missing | `--key and --value are required.` |
180+
| `key` not in valid list | `Invalid config key "${key}". Valid keys: region, base_url, output, timeout, api_key` |
181+
| `region` value invalid | `Invalid region "${value}". Valid values: global, cn` |
182+
| `output` value invalid | `Invalid output format "${value}". Valid values: text, json` |
183+
| `timeout` not a positive number | `Invalid timeout "${value}". Must be a positive number.` |
184+
185+
### `minimax config export-schema`
186+
187+
| Scenario | Error Message |
188+
|---|---|
189+
| `--command` specifies non-existent command | `Command "${targetCommand}" not found.` |
190+
191+
---
192+
193+
## Update Commands
194+
195+
### `minimax update`
196+
197+
No error scenarios — prints a message directing users to run `npm update -g minimax-cli` manually.
198+
199+
---
200+
201+
## File Commands
202+
203+
### `minimax file upload`
204+
205+
| Scenario | Error Message |
206+
|---|---|
207+
| `--file` local file not found | `File not found: ${fullPath}` |
208+
| API error (size limit, unsupported type, etc.) | [API Errors](#api-errors) apply |
209+
210+
### `minimax file delete`
211+
212+
| Scenario | Error Message |
213+
|---|---|
214+
| No `--file-id` in non-interactive mode | `Missing required argument: --file-id` |
215+
216+
### `minimax file list`
217+
218+
| Scenario | Error Message |
219+
|---|---|
220+
| All errors fall under [Network Errors](#networkerrors) | |
221+
222+
---
223+
224+
## Global Errors (All Commands)
225+
226+
### Network Errors
227+
228+
| Scenario | Error Message |
229+
|---|---|
230+
| Network/connection failure | `Network request failed.` + hint: `Check your network connection and proxy settings.` |
231+
| Proxy error detected | `Network request failed.` + hint: `Proxy error — check HTTP_PROXY / HTTPS_PROXY environment variables and proxy authentication.` |
232+
| Request timeout (AbortSignal) | `Request timed out.` |
233+
| HTTP 408 / 504 | `Request timed out (HTTP ${status}).` |
234+
235+
### API Errors
236+
237+
| Scenario | Error Message |
238+
|---|---|
239+
| HTTP 401 / 403 | `API key rejected (HTTP ${status}).` |
240+
| HTTP 429 | `Rate limit or quota exceeded. ${apiMsg}` |
241+
| `status_code` 1002 / 1039 (content filter) | `Input content flagged by sensitivity filter (${filterType}).` |
242+
| `status_code` 1028 / 1030 (quota exhausted) | `Quota exhausted. ${apiMsg}` |
243+
| `status_code` 2061 (model not on plan) | `This model is not available on your current Token Plan. ${apiMsg}` |
244+
| Other API errors | `API error: ${apiMsg} (HTTP ${status})` |
245+
| Non-JSON response body (e.g., gateway 502) | `API returned non-JSON response (${contentType}). Server may be experiencing issues.` |
246+
247+
### File System Errors
248+
249+
| Scenario | Error Message |
250+
|---|---|
251+
| File not found | `File system error: ENOENT: no such file or directory...` + hint |
252+
| Permission denied | `File system error: EACCES: permission denied...` + hint |
253+
| Disk full | `File system error: ENOSPC: no space left on device...` + hint |
254+
| Other FS errors | `File system error: ${err.message}` + hint |
255+
256+
### Process Signals
257+
258+
| Scenario | Error Message |
259+
|---|---|
260+
| Ctrl+C / SIGINT | `Interrupted. Exiting.` (exit code 130) |
261+
262+
### Config / Credentials File Corruption
263+
264+
| Scenario | Behavior |
265+
|---|---|
266+
| `~/.minimax/credentials.json` corrupted | Warning written to stderr; treated as no credentials |
267+
| `~/.minimax/config.json` corrupted | Warning written to stderr; treated as empty config |
268+
269+
### Exit Codes
270+
271+
| Code | Meaning |
272+
|---|---|
273+
| 0 | Success |
274+
| 1 | General error |
275+
| 2 | Usage error (invalid arguments) |
276+
| 3 | Authentication error |
277+
| 4 | Quota error |
278+
| 5 | Timeout |
279+
| 6 | Network error |
280+
| 10 | Content filter |
281+
| 130 | Interrupted (Ctrl+C / SIGINT) |

src/auth/credentials.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ export async function loadCredentials(): Promise<CredentialFile | null> {
1212
const data = JSON.parse(raw) as CredentialFile;
1313
if (!data.access_token || !data.refresh_token) return null;
1414
return data;
15-
} catch {
15+
} catch (err) {
16+
const e = err as Error;
17+
if (e instanceof SyntaxError || e.message.includes('JSON')) {
18+
process.stderr.write(`Warning: credentials file is corrupted. Run 'minimax auth logout' to reset.\n`);
19+
}
1620
return null;
1721
}
1822
}

src/client/http.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { Config } from '../config/schema';
22
import type { ApiErrorBody } from '../errors/api';
3+
import { CLIError } from '../errors/base';
4+
import { ExitCode } from '../errors/codes';
35
import { resolveCredential } from '../auth/resolver';
46
import { mapApiError } from '../errors/api';
57
import { maybeShowStatusBar } from '../output/status-bar';
@@ -78,7 +80,16 @@ export async function request(config: Config, opts: RequestOpts): Promise<Respon
7880

7981
export async function requestJson<T>(config: Config, opts: RequestOpts): Promise<T> {
8082
const res = await request(config, opts);
81-
const data = (await res.json()) as T & { base_resp?: { status_code?: number; status_msg?: string } };
83+
let data: T & { base_resp?: { status_code?: number; status_msg?: string } };
84+
try {
85+
data = (await res.json()) as T & { base_resp?: { status_code?: number; status_msg?: string } };
86+
} catch {
87+
const contentType = res.headers.get('content-type') || '';
88+
throw new CLIError(
89+
`API returned non-JSON response (${contentType || 'unknown type'}). Server may be experiencing issues.`,
90+
ExitCode.GENERAL,
91+
);
92+
}
8293

8394
if (data.base_resp?.status_code && data.base_resp.status_code !== 0) {
8495
throw mapApiError(200, { base_resp: data.base_resp }, opts.url);

src/config/loader.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ export function readConfigFile(): ConfigFile {
99
if (!existsSync(path)) return {};
1010
try {
1111
return parseConfigFile(JSON.parse(readFileSync(path, 'utf-8')));
12-
} catch {
12+
} catch (err) {
13+
const e = err as Error;
14+
if (e instanceof SyntaxError || e.message.includes('JSON')) {
15+
process.stderr.write(`Warning: config file is corrupted. Run 'minimax config set' to reset.\n`);
16+
}
1317
return {};
1418
}
1519
}

src/errors/codes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const ExitCode = {
55
AUTH: 3,
66
QUOTA: 4,
77
TIMEOUT: 5,
8+
NETWORK: 6,
89
CONTENT_FILTER: 10,
910
} as const;
1011

0 commit comments

Comments
 (0)