# `ClaudeWrapper.Auth`
[🔗](https://github.com/genagent/claude_wrapper_ex/blob/main/lib/claude_wrapper/auth.ex#L41)

Detect which auth strategy the Claude Code CLI will use, and classify
auth-related CLI failures.

This is a standalone, env-only introspection module. It is **distinct**
from `ClaudeWrapper.Commands.Auth`, which shells out to `claude auth`
(login/logout/status/setup-token). Nothing here spawns a subprocess or
reads the filesystem.

## Detection

Claude Code resolves auth at invocation time by inspecting a few
environment variables, falling back to credentials stored under
`~/.claude/` when none are set. `detect/0` mirrors that precedence as a
cheap, synchronous, env-only check so hosts can introspect the active
mode before spawning a turn.

It is **not** a liveness check -- a reported `:subscription` strategy
only means "no env auth set"; the user might not have run `claude login`
yet. Use `ClaudeWrapper.Commands.Auth.status/1` for that.

Precedence (first match wins):

  1. `CLAUDE_CODE_USE_BEDROCK` truthy -> `:bedrock`
  2. `CLAUDE_CODE_USE_VERTEX` truthy -> `:vertex`
  3. `ANTHROPIC_API_KEY` non-empty -> `:api_key`
  4. `ANTHROPIC_AUTH_TOKEN` non-empty -> `:auth_token`
  5. `CLAUDE_CODE_OAUTH_TOKEN` non-empty -> `:oauth_token`
  6. Otherwise -> `:subscription`

`ANTHROPIC_API_KEY` is ordered ahead of `ANTHROPIC_AUTH_TOKEN` because the
direct-API key is the more common path; both are env-pinned header
credentials, so their relative order only affects the reported label, not
liveness.

Cloud-provider strategies (Bedrock, Vertex) take precedence because
they redirect ALL traffic regardless of API key presence.

## Failure classification

`classify_failure/3` inspects a failed `claude` invocation and decides
whether it looks auth-shaped. It returns the matching
`t:auth_error_kind/0` atom, or `nil` when the failure is **not**
auth-related. It is conservative on purpose: a false positive turns a
legitimate non-auth failure into an "auth error" surprise, so the
classifier prefers to miss an auth error rather than misclassify a
non-auth one.

Two recent fixes are preserved from the Rust reference:

  * A model-not-found / model-access failure (a bad `--model` id) must
    **not** be classified as auth, even when it carries a 403/404 HTTP
    status. It returns `nil`.
  * A `--bare` invocation with a missing API key prints
    "Not logged in" to stdout with empty stderr; that surfaces as
    `:not_authenticated`.

## Example

    summary = ClaudeWrapper.Auth.detect()
    summary.strategy
    #=> :subscription

    ClaudeWrapper.Auth.classify_failure(1, "", "HTTP 401 Unauthorized")
    #=> :invalid_credentials

    ClaudeWrapper.Auth.classify_failure(1, "no match found", "")
    #=> nil

# `auth_error_kind`

```elixir
@type auth_error_kind() ::
  :not_authenticated
  | :expired
  | :invalid_credentials
  | :rate_limit
  | :provider_error
  | :other
```

Best-effort classification of an auth-related CLI failure.

  * `:not_authenticated` -- no credentials at all. Fix: run
    `claude login` or set one of the auth env vars.
  * `:expired` -- stored credentials existed but are expired. Fix:
    re-run `claude login`.
  * `:invalid_credentials` -- credentials were presented but rejected
    (wrong/revoked key, stale token).
  * `:rate_limit` -- authenticated but rejected for rate limit / quota /
    billing reasons. Different remediation: wait, top up, or switch
    keys -- not "log in again."
  * `:provider_error` -- Bedrock or Vertex provider error (cloud creds
    missing or rejected). The fix lives in the cloud provider's auth.
  * `:other` -- looked auth-shaped (HTTP 401/403, the word "auth") but
    did not match a more specific pattern.

# `strategy`

```elixir
@type strategy() ::
  :bedrock | :vertex | :api_key | :auth_token | :oauth_token | :subscription
```

Active auth strategy, as inferred from the host environment.

  * `:bedrock` -- `CLAUDE_CODE_USE_BEDROCK` is truthy. Requests route to
    AWS Bedrock; AWS credentials are resolved separately by the SDK.
  * `:vertex` -- `CLAUDE_CODE_USE_VERTEX` is truthy. Requests route to
    Google Vertex; GCP credentials are resolved separately.
  * `:api_key` -- `ANTHROPIC_API_KEY` is set. Direct API access.
  * `:auth_token` -- `ANTHROPIC_AUTH_TOKEN` is set. A custom Bearer token
    sent as `Authorization: Bearer` (typically alongside `ANTHROPIC_BASE_URL`
    for a gateway / custom endpoint), distinct from `ANTHROPIC_API_KEY`,
    which is sent as `x-api-key`.
  * `:oauth_token` -- `CLAUDE_CODE_OAUTH_TOKEN` is set (typically from
    `claude setup-token`).
  * `:subscription` -- no auth env var set. The CLI looks for stored
    credentials under `~/.claude/`. May or may not actually be
    authenticated -- this reports "the env doesn't pin anything," not
    "you are logged in."

See the module docs for precedence rules.

# `classify_failure`

```elixir
@spec classify_failure(integer(), String.t(), String.t()) :: auth_error_kind() | nil
```

Inspect a failed `claude` invocation and decide whether it looks
auth-shaped.

Returns the matching `t:auth_error_kind/0` atom only when the patterns
are confident enough to risk relabeling, and `nil` otherwise.

`exit_code` is accepted for parity with the Rust signature and future
use; the current heuristics match only against the lowercased
`stdout`/`stderr` text. The patterns are intentionally narrow:

  * model-not-found / model-access phrasing -> `nil` (a bad `--model`
    id is a typo, not a credential problem, even with a 403/404 status)
  * "not authenticated" / "not logged in" / "claude login" /
    "run /login" / "no credentials" / "no auth" -> `:not_authenticated`
  * "expired" / "session has expired" / "token expired" -> `:expired`
  * "invalid api key" / "invalid token" / "401" / "unauthorized" /
    "403" / "forbidden" -> `:invalid_credentials`
  * "rate limit" / "too many requests" / "429" / "quota" -> `:rate_limit`
  * "bedrock" or "vertex" alongside an auth signal -> `:provider_error`
  * a bare "auth" / "credential" mention in stderr -> `:other`

# `detect`

```elixir
@spec detect() :: ClaudeWrapper.Auth.Summary.t()
```

Detect the active auth strategy from the current process environment.

Cheap; no subprocess, no filesystem reads. Reads the real process env
via `System.get_env/0`.

# `detect_from`

```elixir
@spec detect_from(%{optional(String.t()) =&gt; String.t()}) ::
  ClaudeWrapper.Auth.Summary.t()
```

Like `detect/0`, but reads from a caller-provided env map.

Exposed for tests and for hosts that want to introspect a child
environment they are about to spawn under. The map is keyed by env-var
name, matching the shape of `System.get_env/0`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
