fix(cli): JSON error envelope shows command name + clearer rate-limit message
Two papercuts surfaced when prod's bootstrap returned 403:
{"status": "error", "command": "", "error": "Bootstrap failed: {\"detail\":\"You do not have permission to perform this action.\"}", "data": null}
1. `"command": ""` — the JSON error envelope (printError under agent
mode) reads from current_command state, but nothing ever called
setCurrentCommand on the init subcommand. Hook into Node's
preAction and Python's main_callback to stash the active
subcommand name so error envelopes report which command failed.
2. Opaque rate-limit message. The backend's @ratelimit decorator
raises PermissionDenied → DRF translates to generic 403
"You do not have permission to perform this action." Users don't
know it's a rate limit. Detect status_code==403 with /permission/i
in the detail and surface the actual reason:
"Daily Agent Mode signup limit reached for this network (5/day).
Try again from a different IP or after midnight UTC."
Both fixes mirrored in Python (agent_mode_cmd.py, app.py) and Node
(agent-mode.ts, index.ts).
This commit is contained in:
@@ -64,11 +64,21 @@ export async function bootstrapViaBackend(
|
|||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
let detail: string = resp.statusText;
|
let detail: string = resp.statusText;
|
||||||
try {
|
try {
|
||||||
const errBody = (await resp.json()) as { error?: string };
|
const body = (await resp.json()) as { error?: string; detail?: string };
|
||||||
if (errBody.error) detail = errBody.error;
|
detail = body.error ?? body.detail ?? resp.statusText;
|
||||||
} catch {
|
} catch {
|
||||||
/* leave detail as statusText */
|
/* leave detail as statusText */
|
||||||
}
|
}
|
||||||
|
// Backend's @ratelimit decorator raises PermissionDenied, which DRF
|
||||||
|
// translates to a generic 403 "You do not have permission to perform
|
||||||
|
// this action." That's opaque — surface it as the rate-limit message
|
||||||
|
// it actually is.
|
||||||
|
if (resp.status === 403 && /permission/i.test(detail)) {
|
||||||
|
printError(
|
||||||
|
"Daily Agent Mode signup limit reached for this network (5/day). Try again from a different IP or after midnight UTC.",
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
printError(`Bootstrap failed: ${detail}`);
|
printError(`Bootstrap failed: ${detail}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { colors, printError, printWarning } from "./branding.js";
|
|||||||
import type { Mem0Config } from "./config.js";
|
import type { Mem0Config } from "./config.js";
|
||||||
import { loadConfig, saveConfig } from "./config.js";
|
import { loadConfig, saveConfig } from "./config.js";
|
||||||
import { richFormatHelp } from "./help.js";
|
import { richFormatHelp } from "./help.js";
|
||||||
import { isAgentMode, setAgentMode, takeNotice } from "./state.js";
|
import { isAgentMode, setAgentMode, setCurrentCommand, takeNotice } from "./state.js";
|
||||||
import { captureEvent } from "./telemetry.js";
|
import { captureEvent } from "./telemetry.js";
|
||||||
import { CLI_VERSION } from "./version.js";
|
import { CLI_VERSION } from "./version.js";
|
||||||
|
|
||||||
@@ -171,6 +171,10 @@ program.hook("preAction", (_thisCommand, actionCommand) => {
|
|||||||
parentName && parentName !== "mem0"
|
parentName && parentName !== "mem0"
|
||||||
? `${parentName}.${commandName}`
|
? `${parentName}.${commandName}`
|
||||||
: commandName;
|
: commandName;
|
||||||
|
// Stash the active command name in shared state so the JSON
|
||||||
|
// error envelope (printError) can report which command failed
|
||||||
|
// instead of an empty `"command": ""` field.
|
||||||
|
setCurrentCommand(fullCommand);
|
||||||
// init fires its own telemetry from runInit with full M1-M6 props
|
// init fires its own telemetry from runInit with full M1-M6 props
|
||||||
// (mode/agent_caller/signup_source/claimed_agent_mode); skip the
|
// (mode/agent_caller/signup_source/claimed_agent_mode); skip the
|
||||||
// auto-fire here so we don't double-count.
|
// auto-fire here so we don't double-count.
|
||||||
|
|||||||
@@ -236,6 +236,13 @@ def main_callback(
|
|||||||
_fire_telemetry("version")
|
_fire_telemetry("version")
|
||||||
cmd_version()
|
cmd_version()
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
if ctx.invoked_subcommand:
|
||||||
|
# Stash the active subcommand name so the JSON error envelope
|
||||||
|
# (print_error in agent mode) can report which command failed
|
||||||
|
# instead of an empty `"command": ""` field.
|
||||||
|
from mem0_cli.state import set_current_command
|
||||||
|
|
||||||
|
set_current_command(ctx.invoked_subcommand)
|
||||||
if ctx.invoked_subcommand and ctx.invoked_subcommand != "init":
|
if ctx.invoked_subcommand and ctx.invoked_subcommand != "init":
|
||||||
# init fires its own telemetry from init_cmd.run_init with full M1-M6 props.
|
# init fires its own telemetry from init_cmd.run_init with full M1-M6 props.
|
||||||
_fire_telemetry(ctx.invoked_subcommand)
|
_fire_telemetry(ctx.invoked_subcommand)
|
||||||
|
|||||||
@@ -61,10 +61,21 @@ def bootstrap_via_backend(
|
|||||||
print_error(err_console, "Agent Mode is temporarily disabled. Try again later.")
|
print_error(err_console, "Agent Mode is temporarily disabled. Try again later.")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
|
detail = resp.text
|
||||||
try:
|
try:
|
||||||
detail = resp.json().get("error", resp.text)
|
body = resp.json()
|
||||||
|
detail = body.get("error") or body.get("detail") or resp.text
|
||||||
except Exception:
|
except Exception:
|
||||||
detail = resp.text
|
pass
|
||||||
|
# Backend's @ratelimit decorator raises PermissionDenied, which DRF
|
||||||
|
# translates to a generic 403 "You do not have permission to perform
|
||||||
|
# this action." That's opaque — surface as the rate-limit it actually is.
|
||||||
|
if resp.status_code == 403 and "permission" in str(detail).lower():
|
||||||
|
print_error(
|
||||||
|
err_console,
|
||||||
|
"Daily Agent Mode signup limit reached for this network (5/day). Try again from a different IP or after midnight UTC.",
|
||||||
|
)
|
||||||
|
raise typer.Exit(1)
|
||||||
print_error(err_console, f"Bootstrap failed: {detail}")
|
print_error(err_console, f"Bootstrap failed: {detail}")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user