Skip to content

Commit 2bbf597

Browse files
yeldarbyclaude
andcommitted
CLI: add actionable hints to every error path in the trash commands
CONTRIBUTING.md's Agent Experience checklist asks that every output_error() include an actionable hint. The delete/restore/list commands shipped with hints on some paths but not others. Fills in the gaps so agents get a consistent "what went wrong AND what to do" pair on every failure: - resolver ValueError: tell them the valid shorthand formats - API RoboflowError: point at the specific scope that's likely missing ('project:update' / 'version:update' / 'project:read') - 'not in trash' on version restore: additionally note that a parent project in trash must be restored first No behavior change — same error paths, same exit codes, same JSON schema. Just more useful text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9de0bdf commit 2bbf597

3 files changed

Lines changed: 75 additions & 15 deletions

File tree

‎roboflow/cli/handlers/project.py‎

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,11 @@ def _delete_project(args): # noqa: ANN001
230230
try:
231231
workspace_url, project_slug, _version = resolve_resource(args.project_id, workspace_override=args.workspace)
232232
except ValueError as exc:
233-
output_error(args, str(exc))
233+
output_error(
234+
args,
235+
str(exc),
236+
hint="Use 'my-workspace/my-project' or set --workspace and pass 'my-project'.",
237+
)
234238
return
235239

236240
api_key = args.api_key or load_roboflow_api_key(workspace_url)
@@ -258,7 +262,12 @@ def _delete_project(args): # noqa: ANN001
258262
try:
259263
data = rfapi.delete_project(api_key, workspace_url, project_slug)
260264
except rfapi.RoboflowError as exc:
261-
output_error(args, str(exc), exit_code=3)
265+
output_error(
266+
args,
267+
str(exc),
268+
hint="Check your API key has 'project:update' scope on this workspace.",
269+
exit_code=3,
270+
)
262271
return
263272

264273
output(
@@ -277,7 +286,11 @@ def _restore_project(args): # noqa: ANN001
277286
try:
278287
workspace_url, project_slug, _version = resolve_resource(args.project_id, workspace_override=args.workspace)
279288
except ValueError as exc:
280-
output_error(args, str(exc))
289+
output_error(
290+
args,
291+
str(exc),
292+
hint="Use 'my-workspace/my-project' or set --workspace and pass 'my-project'.",
293+
)
281294
return
282295

283296
api_key = args.api_key or load_roboflow_api_key(workspace_url)
@@ -293,7 +306,12 @@ def _restore_project(args): # noqa: ANN001
293306
try:
294307
trash = rfapi.list_trash(api_key, workspace_url)
295308
except rfapi.RoboflowError as exc:
296-
output_error(args, str(exc), exit_code=3)
309+
output_error(
310+
args,
311+
str(exc),
312+
hint="Check your API key has 'project:read' scope on this workspace.",
313+
exit_code=3,
314+
)
297315
return
298316

299317
datasets = trash.get("sections", {}).get("datasets", [])
@@ -302,15 +320,20 @@ def _restore_project(args): # noqa: ANN001
302320
output_error(
303321
args,
304322
f"Project '{workspace_url}/{project_slug}' is not in Trash.",
305-
hint="Use 'roboflow trash list' to see what can be restored.",
323+
hint="Run 'roboflow trash list' to see what can be restored.",
306324
exit_code=3,
307325
)
308326
return
309327

310328
try:
311329
data = rfapi.restore_trash_item(api_key, workspace_url, "dataset", match["id"])
312330
except rfapi.RoboflowError as exc:
313-
output_error(args, str(exc), exit_code=3)
331+
output_error(
332+
args,
333+
str(exc),
334+
hint="Check your API key has 'project:update' scope on this workspace.",
335+
exit_code=3,
336+
)
314337
return
315338

316339
output(args, data, text=f"Restored {workspace_url}/{project_slug} from Trash.")

‎roboflow/cli/handlers/trash.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,12 @@ def _list_trash(args): # noqa: ANN001
6262
try:
6363
trash = rfapi.list_trash(api_key, workspace_url)
6464
except rfapi.RoboflowError as exc:
65-
output_error(args, str(exc), exit_code=3)
65+
output_error(
66+
args,
67+
str(exc),
68+
hint="Check your API key has 'project:read' scope on this workspace.",
69+
exit_code=3,
70+
)
6671
return
6772

6873
items = trash.get("items", [])

‎roboflow/cli/handlers/version.py‎

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -363,11 +363,19 @@ def _delete_version(args): # noqa: ANN001
363363
try:
364364
workspace_url, project_slug, version_num = resolve_resource(args.version_ref, workspace_override=args.workspace)
365365
except ValueError as exc:
366-
output_error(args, str(exc))
366+
output_error(
367+
args,
368+
str(exc),
369+
hint="Use 'workspace/project/3' or 'project/3' (version must be a number).",
370+
)
367371
return
368372

369373
if version_num is None:
370-
output_error(args, "Version number is required (e.g. project/3 or ws/project/3).")
374+
output_error(
375+
args,
376+
"Version number is required.",
377+
hint="Pass 'project/3' or 'workspace/project/3' — the trailing segment must be the numeric version id.",
378+
)
371379
return
372380

373381
api_key = args.api_key or load_roboflow_api_key(workspace_url)
@@ -395,7 +403,12 @@ def _delete_version(args): # noqa: ANN001
395403
try:
396404
data = rfapi.delete_version(api_key, workspace_url, project_slug, version_num)
397405
except rfapi.RoboflowError as exc:
398-
output_error(args, str(exc), exit_code=3)
406+
output_error(
407+
args,
408+
str(exc),
409+
hint="Check your API key has 'version:update' scope and the version exists.",
410+
exit_code=3,
411+
)
399412
return
400413

401414
output(
@@ -414,11 +427,19 @@ def _restore_version(args): # noqa: ANN001
414427
try:
415428
workspace_url, project_slug, version_num = resolve_resource(args.version_ref, workspace_override=args.workspace)
416429
except ValueError as exc:
417-
output_error(args, str(exc))
430+
output_error(
431+
args,
432+
str(exc),
433+
hint="Use 'workspace/project/3' or 'project/3' (version must be a number).",
434+
)
418435
return
419436

420437
if version_num is None:
421-
output_error(args, "Version number is required (e.g. project/3 or ws/project/3).")
438+
output_error(
439+
args,
440+
"Version number is required.",
441+
hint="Pass 'project/3' or 'workspace/project/3' — the trailing segment must be the numeric version id.",
442+
)
422443
return
423444

424445
api_key = args.api_key or load_roboflow_api_key(workspace_url)
@@ -434,7 +455,12 @@ def _restore_version(args): # noqa: ANN001
434455
try:
435456
trash = rfapi.list_trash(api_key, workspace_url)
436457
except rfapi.RoboflowError as exc:
437-
output_error(args, str(exc), exit_code=3)
458+
output_error(
459+
args,
460+
str(exc),
461+
hint="Check your API key has 'project:read' scope on this workspace.",
462+
exit_code=3,
463+
)
438464
return
439465

440466
versions = trash.get("sections", {}).get("versions", [])
@@ -447,7 +473,8 @@ def _restore_version(args): # noqa: ANN001
447473
output_error(
448474
args,
449475
f"Version '{workspace_url}/{project_slug}/{version_num}' is not in Trash.",
450-
hint="Use 'roboflow trash list' to see what can be restored.",
476+
hint="Run 'roboflow trash list' to see what can be restored. "
477+
"If the parent project is also in Trash, restore the project first.",
451478
exit_code=3,
452479
)
453480
return
@@ -461,7 +488,12 @@ def _restore_version(args): # noqa: ANN001
461488
parent_id=match.get("parentId"),
462489
)
463490
except rfapi.RoboflowError as exc:
464-
output_error(args, str(exc), exit_code=3)
491+
output_error(
492+
args,
493+
str(exc),
494+
hint="Check your API key has 'project:update' scope on this workspace.",
495+
exit_code=3,
496+
)
465497
return
466498

467499
output(

0 commit comments

Comments
 (0)