GeminiCliSdk provides structured error handling through the GeminiCliSdk.Error exception struct and typed error events in streams.
Error Struct
%GeminiCliSdk.Error{
kind: :cli_not_found, # Atom categorizing the error
message: "gemini not found", # Human-readable description
exit_code: nil, # OS process exit code (when available)
cause: nil, # Underlying error (when wrapping)
details: nil, # Additional context
context: nil # Operation context
}Error Kinds
| Kind | Exit Code | Description |
|---|---|---|
:cli_not_found | -- | The gemini binary is not in PATH or GEMINI_CLI_PATH |
:auth_error | 41 | Authentication failed (invalid API key, expired token) |
:input_error | 42 | Invalid input (bad prompt, malformed request) |
:config_error | 52 | Configuration error (invalid settings) |
:user_cancelled | 130 | User cancelled the operation (Ctrl+C) |
:command_failed | other | Generic command failure |
:command_timeout | -- | Command exceeded the timeout |
:no_result | -- | Stream ended without a result event |
On SSH-backed execution_surface values, Gemini is resolved on the remote
host. If the target shell cannot find gemini, the SDK returns
:cli_not_found with the remote stderr attached. If Gemini is installed
outside the remote non-login PATH, pass a remote PATH override in
Options.env.
Synchronous Error Handling
With run/2, errors are returned as {:error, %Error{}}:
case GeminiCliSdk.run("Do something") do
{:ok, text} ->
IO.puts(text)
{:error, %GeminiCliSdk.Error{kind: :auth_error, message: msg}} ->
IO.puts(:stderr, "Authentication failed: #{msg}")
IO.puts(:stderr, "Run: gemini auth login")
{:error, %GeminiCliSdk.Error{kind: :cli_not_found}} ->
IO.puts(:stderr, "Gemini CLI not found. Install it with: npm install -g @google/gemini-cli")
{:error, %GeminiCliSdk.Error{kind: :command_timeout}} ->
IO.puts(:stderr, "Request timed out. Try increasing timeout_ms.")
{:error, %GeminiCliSdk.Error{} = error} ->
IO.puts(:stderr, "Error: #{Exception.message(error)}")
endStreaming Error Handling
In streams, errors appear as Types.ErrorEvent structs:
GeminiCliSdk.execute("Do something")
|> Enum.each(fn event ->
case event do
%GeminiCliSdk.Types.ErrorEvent{severity: "fatal", message: msg} ->
IO.puts(:stderr, "Fatal error: #{msg}")
%GeminiCliSdk.Types.ErrorEvent{severity: severity, message: msg} ->
IO.puts(:stderr, "[#{severity}] #{msg}")
%GeminiCliSdk.Types.MessageEvent{role: "assistant", content: text} ->
IO.write(text)
_ ->
:ok
end
end)Error Events vs Result Events
The stream may contain both error events (mid-stream) and a final result event:
ErrorEventwithseverity: "fatal"indicates an unrecoverable errorResultEventwithstatus: "error"indicates the CLI finished with an errorResultEventwithstatus: "success"indicates success even if warnings occurred
CLI Not Found
If the Gemini CLI is not installed, the SDK detects this before spawning:
# Streaming: emits a single ErrorEvent
events = GeminiCliSdk.execute("hello") |> Enum.to_list()
# => [%Types.ErrorEvent{severity: "fatal", message: "..."}]
# Synchronous: returns {:error, %Error{}}
{:error, error} = GeminiCliSdk.run("hello")Timeouts
Timeout errors occur when the CLI doesn't respond within timeout_ms:
opts = %GeminiCliSdk.Options{timeout_ms: 5_000}
# Streaming: the last event will be an ErrorEvent
events = GeminiCliSdk.execute("Complex task", opts) |> Enum.to_list()
# Synchronous: returns {:error, %Error{}}
{:error, %GeminiCliSdk.Error{}} = GeminiCliSdk.run("Complex task", opts)Raising on Errors
Since GeminiCliSdk.Error implements the Exception behaviour, you can raise it:
case GeminiCliSdk.run("Do something") do
{:ok, text} -> text
{:error, error} -> raise error
endRetry Strategies
The SDK does not include built-in retries. Implement your own:
defmodule MyApp.Gemini do
def run_with_retry(prompt, opts \\ %GeminiCliSdk.Options{}, retries \\ 3) do
case GeminiCliSdk.run(prompt, opts) do
{:ok, _} = success ->
success
{:error, %GeminiCliSdk.Error{kind: :command_timeout}} when retries > 0 ->
Process.sleep(1_000)
run_with_retry(prompt, opts, retries - 1)
{:error, _} = error ->
error
end
end
end