Skip to content

feat(auth): adopt OAuth credentials from co-installed CLIs#407

Open
Destynova2 wants to merge 1 commit into
mainfrom
feat/adopt-system-credentials
Open

feat(auth): adopt OAuth credentials from co-installed CLIs#407
Destynova2 wants to merge 1 commit into
mainfrom
feat/adopt-system-credentials

Conversation

@Destynova2
Copy link
Copy Markdown
Contributor

What

Lets grob reuse the OAuth token a co-installed CLI already holds, instead of forcing a separate browser flow — and self-heal when its own copy is revoked by refresh-token rotation on the shared account.

grob uses the same OAuth apps as the tools it proxies:

  • Codex CLI / Codex.app → ~/.codex/auth.json (OpenAI client app_EMoamEEZ73f0CkXaXp7hrann)
  • Claude Code → macOS keychain Claude Code-credentials (Anthropic client 9d1c250a-…)

Changes

  • src/auth/system_creds.rs (new): source_for, grob_may_refresh, read_token (Codex JSON + Claude keychain), adopt. Codex expires_at is decoded from the access-token JWT exp; Claude from expiresAt (ms).
  • grob connect <provider> --from-system: mirrors the system token into grob's encrypted store (no browser).
  • [auth] adopt_from_system (default false): the refresh daemon re-adopts a needs_reauth token and recovers a terminal refresh failure by re-adoption.

Refresh ownership (key safety constraint)

Refresh rotates the refresh token, so only one process may refresh a given account:

  • Codex → grob-private once adopted → grob refreshes it.
  • Claude → keychain is shared by every Claude Code session → grob mirrors it read-only and never refreshes it (would log all sessions out). Claude Code owns the refresh.

See ADR-0027.

Tests

  • system_creds: source mapping, refresh-ownership rule, Codex/Claude payload parsing (incl. JWT exp), missing-field rejection.
  • refresh_daemon: new pure plan_service verdicts (skip / refresh codex / adopt claude / heal revoked / no-source fallback).
  • cargo test --lib auth:: → 68 passed; clippy clean.

Notes

  • Pre-push cargo-deny fails on a pre-existing stale advisory in deny.toml (RUSTSEC-2025-0134 matches no crate) — unrelated to this change.

🤖 Generated with Claude Code

grob shares OAuth apps with the Codex CLI (~/.codex/auth.json) and Claude
Code (macOS keychain "Claude Code-credentials"). Add
`grob connect <provider> --from-system` to mirror an existing token from
those sources into grob's encrypted store, avoiding a browser flow when a
valid credential already exists locally.

With `[auth] adopt_from_system` (default off), the refresh daemon
self-heals a revoked token by re-adopting the system credential instead
of forcing a manual `connect --force-reauth`. Claude tokens are mirrored
read-only (grob never refreshes them) because the keychain item is shared
by every Claude Code session and refresh rotates the shared refresh
token; Codex tokens become grob-private and are refreshed normally.

See ADR-0027.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Destynova2 Destynova2 enabled auto-merge June 7, 2026 21:47
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