You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(profile): document auth.type=profile + auto_login Beta + ant v1.5.0+
- README + ja/README: third union branch, profile snippet, active_config
caveat, link to upstream PR anthropics/anthropic-cli#45.
- docs/api-key-helper + ja: new "Profile-based authentication" section
(Quick start, Auto-login bootstrap Beta, env-var migration), updated
field table, 401/403 matrix gains a profile column, Recovery
checklist gains every profile_load + auto_login error_class label.
- docs/configuration + ja: profile credential_source value, profile_load
reason row.
- defaults.jsonnet (claude / codex): auth example block now lists the
profile shape with the [Beta] auto_login variant.
- examples/{claude,codex}/ccgate.jsonnet: commented-out profile + Beta
variants alongside the existing provider block.
All copy is honest about the Beta scope: ccgate does not save / restore
active_config in this release; root fix is upstream.
Refs #69
Copy file name to clipboardExpand all lines: README.md
+20-3Lines changed: 20 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -252,7 +252,7 @@ Project-local configs are loaded only when **not tracked by Git**.
252
252
|`provider.name`| string |`"anthropic"`| Provider name. One of `"anthropic"`, `"openai"`, `"gemini"`. |
253
253
|`provider.model`| string |`"claude-haiku-4-5"`| Model name. Examples: `claude-haiku-4-5` / `claude-sonnet-4-6` (anthropic), `gpt-5.4-nano-2026-03-17` (openai), `gemini-3-flash-preview` (gemini). When routing through a compatible proxy, use whatever model name the proxy exposes (e.g. `anthropic/claude-haiku-4-5`). |
254
254
|`provider.base_url`| string |`""`| Override the provider's API base URL. Empty = use the SDK default. Use this to route through an OpenAI- / Anthropic-compatible proxy (LiteLLM proxy, Azure OpenAI, on-prem gateway, regional endpoint, ...). |
255
-
|`provider.auth`| object (`{type, ...}`) | (omit = env var) | Discriminated union for short-lived / rotating credentials. `type=exec` (run a shell command), `type=file` (read a rotator-managed file). See [docs/api-key-helper.md](docs/api-key-helper.md) for full reference. |
255
+
|`provider.auth`| object (`{type, ...}`) | (omit = env var) | Discriminated union for short-lived / rotating credentials. `type=exec` (run a shell command), `type=file` (read a rotator-managed file), `type=profile` (Anthropic-only, reads `ant auth login` credentials; `auto_login: true` is **Beta** and spawns `ant auth login` on credentials-missing). See [docs/api-key-helper.md](docs/api-key-helper.md) for full reference. |
256
256
|`provider.timeout_ms`| int |`20000`| API timeout (ms). `0` = no timeout. |
257
257
|`log_path`| string |`$XDG_STATE_HOME/ccgate/<target>/ccgate.log`| Log file path. Supports `~` for home directory. |
@@ -329,7 +329,7 @@ Export the proxy's API key as `CCGATE_ANTHROPIC_API_KEY`. The Anthropic SDK appe
329
329
330
330
### Short-lived / rotating API keys
331
331
332
-
When the credential rotates faster than a static env var can keep up (AWS STS, Vertex ADC, OpenAI-compatible gateways with virtual keys, internal key brokers), use `provider.auth`. It's a discriminated union over two shapes — pick the one that matches your setup:
332
+
When the credential rotates faster than a static env var can keep up (AWS STS, Vertex ADC, OpenAI-compatible gateways with virtual keys, internal key brokers), use `provider.auth`. It's a discriminated union over three shapes — pick the one that matches your setup:
333
333
334
334
```jsonnet
335
335
// Run a shell helper to mint a credential on demand
@@ -356,18 +356,35 @@ When the credential rotates faster than a static env var can keep up (AWS STS, V
356
356
},
357
357
},
358
358
}
359
+
360
+
// Or read credentials from an `ant auth login` profile (Anthropic only;
361
+
// the SDK refreshes the access token on its own, ccgate stays out of the
// auto_login: true, // [Beta] spawns `ant auth login` on miss; requires ant v1.5.0+
371
+
},
372
+
},
373
+
}
359
374
```
360
375
361
376
The helper / file content is one of:
362
377
363
378
-**JSON**`{"key":"sk-...","expires_at":"<RFC3339>"}` — for `auth.type=exec`, memoized in `$XDG_CACHE_HOME/ccgate/<target>/` and refreshed early.
364
379
-**Plain string** — a single non-empty line, not cached.
365
380
381
+
`auth.type=profile` is different: ccgate hands the loaded profile to anthropic-sdk-go via `option.WithConfig`, and the SDK's refresh-token loop owns the credential lifecycle. ccgate also calls `option.WithoutEnvironmentDefaults` so a leftover `ANTHROPIC_API_KEY` cannot silently shadow your declared profile. After any `ant auth login` (auto-login or manual) check `ant auth status` — `ant` rewrites `<config_dir>/active_config` (shared with Claude Code) as a side effect of `--profile`; upstream PR [anthropics/anthropic-cli#45](https://github.com/anthropics/anthropic-cli/pull/45) adds a `--no-activate` flag to drop that side effect.
382
+
366
383
Resolution order: `provider.auth` (when configured) > `CCGATE_*_API_KEY` > `*_API_KEY`. When `auth` is configured ccgate **does not silently fall back to env vars on failure** — the hook falls through with `kind=credential_unavailable` instead.
367
384
368
385
ccgate also registers `std.native('env')(name)` (returns empty string for undefined) and `std.native('must_env')(name)` (raises a config-load error) as Jsonnet helpers, so any string field can pull values from the environment without ccgate-specific syntax.
369
386
370
-
See [docs/api-key-helper.md](docs/api-key-helper.md) for the full helper contract, runnable examples, account-aware caching via `auth.cache_key`, browser-based first-run auth, the 401/403 behaviour matrix, and the operational recovery checklist.
387
+
See [docs/api-key-helper.md](docs/api-key-helper.md) for the full helper contract, runnable examples, account-aware caching via `auth.cache_key`, browser-based first-run auth, the profile auto-login Beta + `active_config` caveat, the 401/403 behaviour matrix, and the operational recovery checklist.
371
388
## Default Rules
372
389
373
390
ccgate ships built-in default rules per target. They are always applied as the base; your global / project-local configs layer on top.
Copy file name to clipboardExpand all lines: docs/api-key-helper.md
+64-9Lines changed: 64 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -45,14 +45,14 @@ Linux, macOS, *BSD, and Windows are supported. The shell that runs the helper co
45
45
46
46
| Field | Type | Default | Description |
47
47
|---|---|---|---|
48
-
| Field | Type | Default | Description |
49
-
|---|---|---|---|
50
-
|`auth.type`|`"exec"` / `"file"`| (required when `auth` is set) | Resolution mode. |
48
+
|`auth.type`|`"exec"` / `"file"` / `"profile"`| (required when `auth` is set) | Resolution mode. `profile` is Anthropic-only; see [Profile-based authentication](#profile-based-authentication-anthropic-only). |
51
49
|`auth.command`| string |`""`| (`exec` only, required) Shell command; stdout is the credential. |
52
50
|`auth.shell`|`"bash"` / `"powershell"`|`"bash"`| (`exec` only) Shell. `powershell` tries `pwsh` first, falls back to `powershell`. |
53
51
|`auth.path`| string |`$XDG_STATE_HOME/ccgate/<target>/auth_key.json`| (`file` only) Credential file path. Omit to use the default. |
54
-
|`auth.refresh_margin_ms`| int (ms) |`60000`| Treat credentials as expired this many ms before `expires_at`. `0` disables. |
55
-
|`auth.timeout_ms`| int (ms) |`30000`| Hard cap on one Resolve call. `> 0`. |
|`auth.auto_login`| bool |`false`| (`profile` only, **Beta**) When true, ccgate spawns `ant auth login --profile <name>` if the credentials file is missing at preflight. Requires `name` and `ant` v1.5.0+. |
54
+
|`auth.refresh_margin_ms`| int (ms) |`60000`| Treat credentials as expired this many ms before `expires_at`. `0` disables. (`exec` / `file` only — `profile` delegates refresh to the SDK.) |
55
+
|`auth.timeout_ms`| int (ms) |`30000` (`exec` / `file`); `360000` (`profile` + `auto_login`) | Hard cap on one Resolve call (`exec` / `file`) or on the `ant` subprocess (`profile` + `auto_login`, passed to `ant --timeout`; ccgate's CommandContext kill cap is `timeout_ms + 30000`). `> 0`. |
56
56
|`auth.cache_key`| string |`""`| (`exec` only) Salt added to the cache fingerprint. See [Account isolation](#account-isolation). |
57
57
58
58
Relative paths in `auth.command` and `auth.path` resolve from the hook's working directory at fire time, not from the config file's directory.
@@ -86,6 +86,47 @@ Helpers like `gcloud auth print-access-token`, `aws sso login`, or an internal S
86
86
87
87
This pattern is supported. The default `auth.timeout_ms` (`30000`) covers most non-interactive helpers; for browser-based first-run flows that involve the user clicking through a consent screen, raise it to e.g. `120000` so the first Permission Request after a long idle does not surface as `reason=timeout`.
88
88
89
+
## Profile-based authentication (Anthropic only)
90
+
91
+
Anthropic provider only. The official `ant` CLI (`ant auth login` for browser-based OAuth, `ant profile activate <name>` to switch the active profile) writes credentials to `<config_dir>/credentials/<name>.json` (mode 0600). `<config_dir>` defaults to `~/.config/anthropic`. The Anthropic profile resolution chain (`$ANTHROPIC_PROFILE` → `<config_dir>/active_config` → `"default"`) is shared between the Go SDK, Claude Code, and the Claude Agent SDK ([wif-reference doc](https://platform.claude.com/docs/en/api/authentication/wif-reference)). The Anthropic SDK refreshes the access token itself using the stored refresh token; ccgate stays out of the credential path entirely and only disables the SDK's static-credential env autoload (`ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`) so a leftover env var does not silently shadow your declared profile. Workload Identity Federation is supported only via a profile config that declares `authentication.type=oidc_federation`; env-only WIF (no profile) is out of scope today.
92
+
93
+
### Quick start
94
+
95
+
```sh
96
+
brew install anthropics/tap/ant # or download a release from anthropics/anthropic-cli
97
+
ant auth login --profile ccgate # opens a browser, writes ~/.config/anthropic/credentials/ccgate.json
> **⚠ Recommended — non-default profile name on Claude Max / Pro**: `ant auth login` without `--profile` creates a `default` profile and points `<config_dir>/active_config` at it. Because Anthropic profile resolution is shared with Claude Code, reusing `default` for ccgate can route Claude Code's credential lookup through the same OAuth profile and shift it from your subscription onto pay-as-you-go API billing. Use a non-default profile name (e.g. `ccgate`) and declare it explicitly in ccgate.jsonnet.
105
+
106
+
### Auto-login bootstrap (opt-in, **Beta**)
107
+
108
+
**Prerequisites**: `ant` v1.5.0 or newer on `PATH`. Verify with `ant --version`.
# the first hook fire after credentials go missing spawns
116
+
# `ant auth login --profile ccgate --timeout 6m`
117
+
# (a browser opens for OAuth approval)
118
+
```
119
+
120
+
`auto_login: true` requires a non-empty `name`; ccgate validates this so the bootstrap never silently writes a `default` profile. `auth.timeout_ms` (default `360000` ms = 6 min) becomes the value passed to `ant --timeout`, and ccgate's CommandContext kill cap is that value + 30 s. Per-profile flock (target-agnostic; under `$XDG_STATE_HOME/ccgate/auto_login.<sha>.lock`) keeps two concurrent hook fires from racing the same browser callback. When ant is missing or fails, ccgate falls through with `kind=credential_unavailable, reason=profile_load`; `error_class` in the slog warning narrows the cause.
121
+
122
+
> **🚧 Beta — `active_config` side effect not mitigated**: `ant auth login --profile <name>` rewrites `<config_dir>/active_config` to `<name>` unconditionally, and `<config_dir>/active_config` is shared with Claude Code and the Claude Agent SDK. ccgate **does not** save / restore `active_config` in this release; mitigating the race in ccgate would itself be racy. The proper fix is upstream PR [anthropics/anthropic-cli#45](https://github.com/anthropics/anthropic-cli/pull/45) (`--no-activate` flag); once that lands, a follow-up ccgate PR will pass the new flag and drop this warning. After auto-login (or a manual `ant auth login`) always run `ant auth status` to confirm the active profile and, if needed, `ant profile activate <claude-profile>` to point Claude Code back. Also: `ant` is invoked via `PATH` lookup, so the binary your `PATH` resolves to must be the trusted upstream `ant` — declaring `auth.auto_login` opts you into trusting that lookup.
123
+
124
+
### Migrating from env-var auth
125
+
126
+
- The hook behaves exactly like before until you add `auth: { type: 'profile', ... }` (env var path stays in effect).
127
+
- Once `auth.type=profile` is declared, ccgate disables the SDK's env autoload, so a leftover `ANTHROPIC_API_KEY` cannot silently shadow your profile.
128
+
- Removing the `auth` block reverts to the env-var path immediately; no cache or state is left behind.
129
+
89
130
## Examples
90
131
91
132
### Wrap an existing env-var credential
@@ -194,10 +235,10 @@ ccgate emits a `slog.Warn` when `auth.path` *or* the cache file has any group/ot
194
235
195
236
When the provider rejects the credential ccgate just used, the HTTP status alone determines the reaction.
196
237
197
-
| HTTP status |`auth.type=exec`|`auth.type=file`| env var |
The env-var path keeps the existing exit-1 behaviour on 401/403 because ccgate cannot rotate env vars; swallowing the rejection would hide a user-side configuration error.
203
244
@@ -218,5 +259,19 @@ To opt out of caching, return JSON without `expires_at` (or plain string) and th
218
259
4. For `expired`, compare the helper's `expires_at` with `date -u`. Clock skew or a broken TTL inside the helper is the usual cause.
219
260
5. For `command_exit` on a fresh setup, check first whether the configured `auth.shell` binary is on `$PATH`. `bash` is universal on Linux / macOS; for `powershell`, at least one of `pwsh` (preferred) or `powershell` must resolve via `$PATH`. When ccgate cannot find the shell at all, the failure surfaces as `command_exit` from `os/exec`'s lookup error.
220
261
6. For repeated `provider_auth` even after cache invalidation, the helper itself is producing a credential the provider rejects. Re-run the helper manually with the same shell ccgate would use — `bash -c "$your_command"`, or `pwsh -Command "$your_command"` / `powershell -Command "$your_command"` for `auth.shell: 'powershell'` — and inspect the stdout that reached the SDK.
262
+
7. For `profile_load` (`auth.type=profile`), the slog `error_class` field narrows the cause:
263
+
-`profile_config_missing`: run `ant auth login --profile <name>` to create the profile.
264
+
-`profile_config_parse` / `profile_config_invalid`: restore `<config_dir>/configs/<name>.json` to mode `0644`, or delete it and re-run `ant auth login --profile <name>`.
265
+
-`credentials_missing` (`auto_login=false`): run `ant auth login --profile <name>` to publish credentials.
266
+
-`credentials_stat_failed`: the credentials file or its parent dir failed `os.Stat` for a reason other than "missing" (typically permissions). Restore mode 0700 on `<config_dir>/credentials/`.
267
+
-`auto_login_requires_profile`: declare `auth.name` (the bootstrap requires a non-default profile name).
268
+
-`credentials_path_auto_login_unsupported`: the profile config sets a custom `credentials_path`; auto-login only knows the SDK default. Either drop the custom path or set `auto_login: false`.
269
+
-`ant_lock_unavailable`: another hook fire is bootstrapping the same profile; the next fire reads the published credentials.
-`ant_lookup_failed`: the `ant` entry on `PATH` is not a working executable; check with `which ant` / `ls -l $(which ant)`.
272
+
-`ant_timeout`: nobody approved the browser login within `auth.timeout_ms`; raise it or run `ant auth login --profile <name>` manually.
273
+
-`ant_failed`: ant exited non-zero — re-run it manually to inspect the error.
274
+
-`credentials_missing_after_login`: ant exited 0 but no credentials file appeared (rare ant bug). Run `ant auth status`.
275
+
8. After any `ant auth login` (auto-login or manual), confirm `<config_dir>/active_config` with `ant auth status`. If Claude Code starts using an unexpected profile, run `ant profile activate <claude-profile>`. Upstream PR [anthropics/anthropic-cli#45](https://github.com/anthropics/anthropic-cli/pull/45) (`--no-activate`) will let ccgate stop touching `active_config` once it lands.
221
276
222
277
The full reason list is in [docs/configuration.md](configuration.md#reason-values-for-credential_unavailable).
0 commit comments