Skip to content

feat(anthropic): auth.type=profile (delegate credentials to the Anthropic SDK)#76

Merged
tak848 merged 10 commits into
mainfrom
feat/anthropic-profile-auth
May 7, 2026
Merged

feat(anthropic): auth.type=profile (delegate credentials to the Anthropic SDK)#76
tak848 merged 10 commits into
mainfrom
feat/anthropic-profile-auth

Conversation

@tak848

@tak848 tak848 commented May 7, 2026

Copy link
Copy Markdown
Owner

Why

Anthropic shipped keyless auth on 2026-05-04: the official ant CLI (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 read ANTHROPIC_API_KEY or run a provider.auth helper (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. Empty profile defers to the SDK resolution chain (ANTHROPIC_PROFILE env var, then active_config, then "default"); a named profile bypasses it via LoadProfile. The loaded config flows into option.WithConfig + option.WithoutEnvironmentDefaults so a leftover ANTHROPIC_API_KEY cannot 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_load fallthrough, with a sanitized error_class slog 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_config pointer file. ant auth login --profile NAME retargets the active pointer unconditionally and that file is shared with Claude Code / Claude Agent SDK; docs/api-key-helper.md spells out the recovery (delete the pointer to clear it, or ant profile activate PREVIOUS to switch). An upstream --no-activate flag would let ant skip the retarget; tracked separately, out of scope for this repo.

Out of scope (followup)

  • Optional auto-login bootstrap (ccgate spawning ant auth login on 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-activate flag landing first would also let it come back simpler.
  • refresh_token expiry surfacing: belongs in feat(observability): ccgate doctor connectivity check + best-effort async notifications #67. The SDK does not export the typed errors today.

Refs #69.

tak848 added 4 commits May 7, 2026 11:52
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).
@tak848 tak848 force-pushed the feat/anthropic-profile-auth branch from 7587171 to a079fc1 Compare May 7, 2026 03:48
tak848 added 2 commits May 7, 2026 13:38
…_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.
@tak848 tak848 changed the title feat(anthropic): auth.type=profile + opt-in auto_login bootstrap feat(anthropic): auth.type=profile (delegate credentials to the Anthropic SDK) May 7, 2026
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.
tak848 added 3 commits May 7, 2026 20:16
- 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)
@tak848 tak848 marked this pull request as ready for review May 7, 2026 11:24
@tak848 tak848 merged commit 5cec883 into main May 7, 2026
4 checks passed
@tak848 tak848 deleted the feat/anthropic-profile-auth branch May 7, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant