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

KindExit CodeDescription
:cli_not_found--The gemini binary is not in PATH or GEMINI_CLI_PATH
:auth_error41Authentication failed (invalid API key, expired token)
:input_error42Invalid input (bad prompt, malformed request)
:config_error52Configuration error (invalid settings)
:user_cancelled130User cancelled the operation (Ctrl+C)
:command_failedotherGeneric 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)}")
end

Streaming 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:

  • ErrorEvent with severity: "fatal" indicates an unrecoverable error
  • ResultEvent with status: "error" indicates the CLI finished with an error
  • ResultEvent with status: "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
end

Retry 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