4242 ZipSink ,
4343 bulk_export_exit_code ,
4444 dominant_failure_code ,
45+ failure_code_for_exception ,
4546 failure_message_for_code ,
4647 run_bulk_export ,
4748 serialize_manifest_jsonl ,
@@ -182,7 +183,10 @@ def cmd_list(args):
182183 project_filter = getattr (args , "project" , None )
183184
184185 if not os .path .isdir (base_dir ):
185- _die (ErrorCode .INTERNAL_ERROR , detail = f"Claude Code projects directory not found: { base_dir } " )
186+ _die (
187+ ErrorCode .INTERNAL_ERROR ,
188+ detail = f"Claude Code projects directory not found: { base_dir } " ,
189+ )
186190
187191 projects = list_projects (base_dir )
188192 if project_filter :
@@ -228,8 +232,13 @@ def _list_sessions(project: dict):
228232 tokens = meta ["total_input_tokens" ] + meta ["total_output_tokens" ]
229233 tools = meta ["total_tool_calls" ]
230234 print (f" { ts :<12} { title :<50} { sid :>10} { tokens :>10,} { tools :>6} " )
231- except Exception as e :
232- print (f" Warning: failed to parse { s ['id' ][:10 ]} : { e } " , file = sys .stderr )
235+ except Exception as exc :
236+ code = failure_code_for_exception (exc )
237+ print (
238+ f" Warning: failed to parse { s ['id' ][:10 ]} : "
239+ f"{ code .value } — { failure_message_for_code (code )} " ,
240+ file = sys .stderr ,
241+ )
233242 continue
234243
235244
@@ -241,7 +250,10 @@ def cmd_stats(args):
241250 fmt = getattr (args , "format" , "text" ) or "text"
242251
243252 if not os .path .isdir (base_dir ):
244- _die (ErrorCode .INTERNAL_ERROR , detail = f"Claude Code projects directory not found: { base_dir } " )
253+ _die (
254+ ErrorCode .INTERNAL_ERROR ,
255+ detail = f"Claude Code projects directory not found: { base_dir } " ,
256+ )
245257
246258 if session_id :
247259 _session_stats (session_id , base_dir , fmt )
@@ -366,9 +378,11 @@ def _aggregate_stats(base_dir: str, project_filter: str, fmt: str):
366378 if cost is not None :
367379 totals ["total_cost" ] += cost
368380 totals ["has_cost" ] = True
369- except Exception as e :
381+ except Exception as exc :
382+ code = failure_code_for_exception (exc )
370383 print (
371- f" Warning: failed to parse { s ['id' ][:10 ]} in { project ['name' ]} : { e } " ,
384+ f" Warning: failed to parse { s ['id' ][:10 ]} in { project ['name' ]} : "
385+ f"{ code .value } — { failure_message_for_code (code )} " ,
372386 file = sys .stderr ,
373387 )
374388 continue
@@ -446,7 +460,10 @@ def cmd_export(args):
446460 exclusion_rules_path = getattr (args , "exclude_rules" , None )
447461
448462 if not os .path .isdir (base_dir ):
449- _die (ErrorCode .INTERNAL_ERROR , detail = f"Claude Code projects directory not found: { base_dir } " )
463+ _die (
464+ ErrorCode .INTERNAL_ERROR ,
465+ detail = f"Claude Code projects directory not found: { base_dir } " ,
466+ )
450467
451468 rules = load_rules (resolve_exclusion_rules_path (exclusion_rules_path ))
452469
@@ -757,8 +774,15 @@ def _save_state(sessions: dict, count: int, out_dir: str):
757774 atomic_write_export_state (disk , STATE_FILE )
758775
759776
777+ def _cli_die_message (code : ErrorCode ) -> str :
778+ """Stable CLI stderr label (not export-scoped)."""
779+ if code == ErrorCode .INTERNAL_ERROR :
780+ return "Command failed"
781+ return failure_message_for_code (code )
782+
783+
760784def _die (code : ErrorCode , * , detail : str | None = None ) -> None :
761- print (f"Error: { code .value } — { failure_message_for_code (code )} " , file = sys .stderr )
785+ print (f"Error: { code .value } — { _cli_die_message (code )} " , file = sys .stderr )
762786 if detail :
763787 print (detail , file = sys .stderr )
764788 sys .exit (1 )
0 commit comments