feat(anthropic): auth.type=profile (delegate credentials to the Anthropic SDK)#76
Merged
Conversation
Wire the Anthropic SDK's keyless auth surface (interactive OAuth profiles produced by `ant auth login`) through ccgate as a first-class `auth.type=profile`. The SDK refreshes access tokens on its own; ccgate just calls anthropicconfig.LoadProfile / LoadConfig and feeds the resulting *Config into option.WithConfig + WithoutEnvironmentDefaults so a leftover ANTHROPIC_API_KEY does not silently shadow a declared profile. When `auth.auto_login: true` is opted into (Beta), ccgate spawns `ant auth login --profile <name>` on credentials-missing preflight. Per-profile flock (target-agnostic) keeps two concurrent hook fires from racing the same browser callback. ccgate intentionally does NOT save / restore active_config: ant overwrites it as a `--profile` side effect, and the proper fix is upstream PR the upstream --no-activate flag PR (`--no-activate`). The Beta marker comes off once ccgate passes that flag. This commit covers the core wiring (config / validate / keystore vocabulary / anthropic.Client / runner). Tests, schemas, and docs land in follow-up commits. Refs #69
…chemas Adds the full anthropic.Client test matrix for the new auth.type=profile path: load paths (named profile, default via active_config, env-shadow defense, base_url override, ANTHROPIC_PROFILE bypass), load failures (credentials missing, profile name validation, config missing/parse), preflight stat failure, auto_login success, auto_login error_class table (ant_not_found, ant_failed, ant_timeout, credentials_missing_after_login, custom credentials_path rejection, auto_login_requires_profile defense), per-profile flock race, provider/auth timeout split, signal cancellation propagation, and ant_lookup_failed. Tests can't run t.Parallel() because t.Setenv mutates process env (ANTHROPIC_CONFIG_DIR, ANTHROPIC_API_KEY, PATH, FAKE_ANT_*). fakeant lives under testdata/ and is built lazily into a per-test TempDir so PATH wiring is contained. Also rolls in: - config_test profile validate matrix + cross-field provider check - runner decide_integration_test profile fallthrough cases (ErrProfileUnavailable -> profile_load, 401/403 -> provider_auth) - schema regeneration + manual root schema (ccgate.schema.json) third branch + schema_test pin from 2 -> 3 branches Refs #69
…5.0+
- README + ja/README: third union branch, profile snippet, active_config
caveat, link to upstream PR the upstream --no-activate flag PR.
- 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
… Windows skips CI on ubuntu/macos failed: TestAutoLoginFailures/ant_times_out allowed fakeant to sleep for FAKE_ANT_SLEEP=30s with --timeout 200ms, but bootstrapWithAnt's CommandContext kill cap is `AutoLoginTimeout + autoLoginKillCapMargin (30s)` so the subprocess ran to completion, ccgate emitted "ant auto-login succeeded", the real Anthropic API was hit, and the test asserted on a 401 instead of ErrProfileUnavailable. Reproducing a real CommandContext deadline-exceeded would require a sleep past 30 s, which is too slow for CI. Drop the subprocess case and pin the ant_timeout taxonomy via a unit test that hands a pre-cancelled ctx straight into classifyAntError — same coverage, no subprocess. Also: skip TestPreflightStatFailure on windows (chmod 0 does not produce a stat failure under the NTFS DACL model the test relies on); remove the now-orphan profileSetup struct (unused, golangci-lint unused-rule).
7587171 to
a079fc1
Compare
…_config retarget The two callouts in the Profile-based authentication section used ad-hoc emoji prefixes (🚧 / ⚠). Switch to GitHub's [!IMPORTANT] / [!WARNING] alert syntax so they render natively in repository views. Also rework the IMPORTANT callout: the previous wording read as "use a non-default profile name and you're safe", but ant rewrites <config_dir>/active_config to whatever --profile <name> is on every login -- the choice of name doesn't avoid the retarget, it only makes recovery cheaper. The WARNING callout now cross-references the IMPORTANT one so the auto_login Beta block doesn't reintroduce the same misreading.
…me -> profile
scope reduction prompted by manual e2e: SDK transparently refreshes the
access token from the stored refresh_token (verified by setting
expires_at to the past), so the auto_login path -- ccgate spawning
`ant auth login` on credentials-missing -- carries the active_config
retarget side effect and per-profile flock complexity for very little
practical benefit. The first-time `ant auth login` is a one-shot
manual step the user already runs anyway.
Removed:
- AuthConfig.AutoLogin / DefaultAuthAutoLoginTimeoutMS /
GetAutoLoginTimeout
- Client.AutoLogin / Client.AutoLoginTimeout
- bootstrapWithAnt / classifyAntError / autoLoginLockPath /
stateBaseDir helper functions
- Decide's 2-stage timeout split (1 stage is enough now)
- 9 auto_login error_class values from the slog vocabulary
- testdata/fakeant binary + 7 auto_login-driven test functions
- Beta marker, Quick start (Beta) section, WARNING callout
Renamed AuthConfig.Name -> AuthConfig.Profile and JSON tag name ->
profile. The previous spelling read ambiguously next to type=exec /
type=file's value-named fields (`command`, `path`); `auth.profile` is
named after the field's job ("the profile to load") and aligns with
the CLI surface where ant talks about "--profile <name>" too.
Future work tracked separately:
- Optional auto-login bootstrap (low priority): file as a separate
issue once a user actually asks; the upstream `--no-activate`
flag PR being open also makes a future re-introduction cleaner.
- refresh_token expiry user notification: belongs in #67
(`ccgate doctor` + best-effort async notifications); refresh
failure today reaches the user as a hook exit 1 because the SDK's
internal/auth typed errors are unexported.
aqua-registry v4.508.0 added anthropics/anthropic-cli, so the install line under the Profile-based authentication section can use the same mise/aqua pattern ccgate itself documents (`mise use -g aqua:...`). Drops the brew tap reference, which was the only install instruction that did not match the repo's mise-first convention.
- Remove the "Migrating from env-var auth" section (EN + JA). It read as a Before/After delta against the previous behavior, which the docs convention does not allow -- documents describe the current state, not the change. The state pieces it tried to convey (env autoload disabled when auth.type=profile is set, env-var path active when auth is omitted) are already implied by the field table and the top paragraph of the Profile section. - Tighten the Japanese phrasing in docs/ja/api-key-helper.md and docs/ja/README.md so non-Japanese verbs / nouns that read unnaturally mid-sentence get translated, while everyday-engineer loanwords stay as-is. Rewrites: shadow -> 黙って上書き, pointer -> 参照先, retarget -> 書き換え, bind -> 紐づけ, land -> マージ, skip -> 回避. Untouched: credential / refresh / fallback / fallthrough / cache / helper / profile / SDK / option / loop / etc. - Also drops a stale "auto-login Beta" mention from docs/ja/README.md that survived the auto_login removal commit.
Same translation rule the previous commit applied to api-key-helper.md extended across the rest of docs/ja/: silent -> 黙って, reject -> 拒否, skip -> 省略 / 回避, fire -> hook 発火. Files touched (configuration.md, codex.md) were not strictly part of PR #76's scope, but they share the broken phrasing the PR work first surfaced; punting the same fix to a separate PR would have been arbitrary. Loanwords engineers actually reach for in Japanese (cache, fallback, fallthrough, helper, credential, etc.) stay as-is.
Previous commit over-translated `silent` to 黙って. The user's rule is finer-grained: adverbs / nouns engineers reach for in Japanese (silent, async, idle, fallback, ...) stay English; verbs that are not idiomatic in Japanese (shadow, retarget, bind, land, skip, reject, pointer) get translated. Reverts: - configuration.md `silent に drop` / `silent に通す` (was 黙って drop / 黙って通す) - api-key-helper.md / README.md `silent に上書き` (was 黙って上書き; `shadow` -> 上書き is kept, only the adverb form returns to English)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Anthropic shipped keyless auth on 2026-05-04: the official
antCLI (ant auth login --profile NAME) writes credentials to the SDK config dir, and anthropic-sdk-go v1.39.0+ refreshes the access token in-process from the stored refresh token. Until this PR, ccgate could only readANTHROPIC_API_KEYor run aprovider.authhelper (type=exec/type=file), so users on Claude Max / Pro plans who already had an OAuth-managed profile on disk had nowhere to declare it.What
Bumps anthropic-sdk-go to v1.40.0 and adds a first-class
provider.auth = { type: 'profile', profile: 'NAME' }Anthropic-only path. Emptyprofiledefers to the SDK resolution chain (ANTHROPIC_PROFILEenv var, thenactive_config, then"default"); a named profile bypasses it viaLoadProfile. The loaded config flows intooption.WithConfig+option.WithoutEnvironmentDefaultsso a leftoverANTHROPIC_API_KEYcannot silently shadow a declared profile. The SDK refreshes access tokens itself; ccgate stays out of the credential lifecycle entirely.Failures the SDK can surface before its request middleware (profile config missing / parse error / SDK profile-name rejection / credentials file missing or unreadable at preflight) become
kind=credential_unavailable, reason=profile_loadfallthrough, with a sanitizederror_classslog field for triage. SDK request-time refresh failures stay on the existing exit-1 path because the typed errors live in the SDK internal package and cannot be asserted from outside; that user-facing surface is tracked in #67 (ccgate doctor+ best-effort async notifications).ccgate intentionally does NOT touch the SDK
active_configpointer file.ant auth login --profile NAMEretargets the active pointer unconditionally and that file is shared with Claude Code / Claude Agent SDK;docs/api-key-helper.mdspells out the recovery (delete the pointer to clear it, orant profile activate PREVIOUSto switch). An upstream--no-activateflag would letantskip the retarget; tracked separately, out of scope for this repo.Out of scope (followup)
ant auth loginon credentials-missing). Was implemented and removed in this branch history. Manual e2e verified the SDK refreshes transparently from the stored refresh token, so the auto-login complexity was not worth carrying for a one-shot first-time setup. Will be re-evaluated as a separate issue if users actually ask. The upstream--no-activateflag landing first would also let it come back simpler.ccgate doctorconnectivity check + best-effort async notifications #67. The SDK does not export the typed errors today.Refs #69.