···3838 | None ->
3939 Fmt.epr "%a No server specified and not logged in.@." error_style "Error:";
4040 Fmt.epr "Use --server or login first.@.";
4141- exit 1
4141+ raise (Immich_auth.Error.Exit_code 1)
4242 in
4343 (* Create session using requests config *)
4444 let session = Requests.Cmd.create requests_config env sw in
+6
bin/main.ml
···3131 in
3232 Cmd.eval (Cmd.group info cmds)
3333 with
3434+ | Eio.Cancel.Cancelled Stdlib.Exit ->
3535+ (* Eio wraps Exit in Cancelled when a fiber is cancelled *)
3636+ 0
3737+ | Immich_auth.Error.Exit_code code ->
3838+ (* Exit code from Error.wrap - already printed error message *)
3939+ code
3440 | Openapi.Runtime.Api_error _ as exn ->
3541 (* Handle Immich API errors with nice formatting *)
3642 Immich_auth.Error.handle_exn exn
+5-4
lib/cmd.ml
···8888 error_style "Error:"
8989 profile_style profile_name
9090 Fmt.(styled `Bold string) "immich auth login";
9191- exit 1
9191+ raise (Error.Exit_code 1)
9292 | Some session -> f fs session
93939494let with_client ?requests_config ?profile f env =
···161161 | Some _, Some _ ->
162162 Fmt.epr "%a Cannot specify both --api-key and --email. Choose one authentication method.@."
163163 error_style "Error:";
164164- exit 1
164164+ raise (Error.Exit_code 1)
165165166166let login_cmd env fs =
167167 let doc = "Login to an Immich server." in
168168 let info = Cmd.info "login" ~doc in
169169 let login' (style_renderer, level) requests_config server api_key email password profile key_name =
170170 setup_logging_with_config style_renderer level requests_config;
171171- login_action ~requests_config ~server ~api_key ~email ~password ~profile ~key_name env
171171+ Error.wrap (fun () ->
172172+ login_action ~requests_config ~server ~api_key ~email ~password ~profile ~key_name env)
172173 in
173174 Cmd.v info
174175 Term.(const login' $ setup_logging $ requests_config_term fs $ server_arg $ api_key_arg $ email_arg $ password_arg $ profile_arg $ key_name_arg)
···302303 Fmt.epr "%a %a@."
303304 label_style "Available profiles:"
304305 Fmt.(list ~sep:(any ", ") profile_style) profiles;
305305- exit 1
306306+ raise (Error.Exit_code 1)
306307 end
307308308309let profile_switch_cmd env =
+16-4
lib/error.ml
···117117 try f (); 0
118118 with exn -> handle_exn exn
119119120120+(** Exception to signal desired exit code without calling [exit] directly.
121121+ This avoids issues when running inside Eio's event loop. *)
122122+exception Exit_code of int
123123+120124(** Wrap a command action to handle API errors gracefully.
121125122126 This is designed to be used in cmdliner command definitions:
···133137 ]}
134138135139 The wrapper catches API errors and prints a nice message,
136136- then exits with an appropriate code. *)
140140+ then raises [Exit_code] with an appropriate code. This exception
141141+ should be caught by the main program outside the Eio event loop. *)
137142let wrap f =
138143 try f ()
139139- with exn ->
140140- let code = handle_exn exn in
141141- exit code
144144+ with
145145+ | Stdlib.Exit ->
146146+ (* exit() was called somewhere - treat as success *)
147147+ ()
148148+ | Eio.Cancel.Cancelled Stdlib.Exit ->
149149+ (* Eio wraps Exit in Cancelled - treat as success *)
150150+ ()
151151+ | exn ->
152152+ let code = handle_exn exn in
153153+ raise (Exit_code code)
+9-2
lib/error.mli
···53535454(** {1 Exception Handling} *)
55555656+exception Exit_code of int
5757+(** Exception raised to signal a desired exit code.
5858+ This is used instead of calling [exit] directly to avoid issues
5959+ when running inside Eio's event loop. Catch this exception in
6060+ the main program outside the Eio context. *)
6161+5662val handle_exn : exn -> int
5763(** Handle an exception, printing a nice error message if it's an API error.
5864···8288(** Wrap a command action to handle API errors gracefully.
83898490 This is designed to be used in cmdliner command definitions.
8585- Catches API errors, prints a nice message, and exits with
8686- an appropriate code.
9191+ Catches API errors, prints a nice message, and raises {!Exit_code}
9292+ with an appropriate code. The calling code should catch this
9393+ exception outside the Eio event loop.
87948895 Usage:
8996 {[