Skip to content

cli: configure and launch with a custom OAuth app - #518

Draft
andy-xu-db wants to merge 1 commit into
andy-xu_data/stack/oauth-auth-token-flagfrom
andy-xu_data/stack/oauth-configure-client-id
Draft

cli: configure and launch with a custom OAuth app#518
andy-xu-db wants to merge 1 commit into
andy-xu_data/stack/oauth-auth-token-flagfrom
andy-xu_data/stack/oauth-configure-client-id

Conversation

@andy-xu-db

@andy-xu-db andy-xu-db commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Completes the feature (the ticket's step 3): ug claude --oauth-client-id <id> — and ug codex, bare ug, and ug configure --oauth-client-id — signs in through a custom OAuth app, and every agent config it writes keeps minting from that same app.

ucode auth-token --host https://dbc-xxxx.cloud.databricks.com --profile dbc-xxxx --oauth-client-id <id>

is what lands in Claude's apiKeyHelper afterwards.

Persisted before it authenticates. configure_shared_state writes the client id into the workspace's state ahead of the login and gateway probe that follow, because both resolve the id from that entry. It follows the "None means inherit" convention already used by use_pat and fable_enabled, so a launch re-run keeps the workspace's app without repeating the flag; --oauth-client-id "" clears it and goes back to the built-in databricks-cli app.

Generated configs pin the id rather than leaving it to run-time resolution: an agent runs its token helper as a bare command line, so the app has to be named in the command itself. state_oauth_client_id reads it out of the state the writers already hold, for

  • Claude's apiKeyHelper,
  • Codex's auth.args (current and legacy overlay),
  • opencode's auth plugin,
  • the registered ucode mcp-proxy argv for every MCP client, and
  • both smart-routing hooks, which re-mint in their own processes.

Cache-bypass guard. _can_launch_from_cached_config rejects an --oauth-client-id that disagrees with what the cached config was written against — reusing it would launch the agent with a token helper pointing at the other OAuth app.

doctor.py and smart_routing/v2.py need no change: the former's has_valid_databricks_auth resolves the id from saved state by host, and the latter only reads the helper's executable path. Claude's relayed mode is also unaffected — it omits apiKeyHelper entirely.

Jira: AIGTWY-4550

Rollout note

The flag only selects the app; the 90-day TTL is a property of the app integration itself. A custom app integration is created with refresh_token_ttl_in_minutes = 10080 (7 days) by default, so it has to be set to 129600 on the integration for this to buy anything.

Testing

  • uv run pytest — 2,349 passed, 38 skipped; the 21 failures are origin/main's pre-existing ones (unchanged), so this PR adds 27 passing tests. Across the stack: 2,249 → 2,349 passing, same 21 failures at every level.
  • uv run ruff check ., uv run ruff format --check src tests, uv run ty check src/.
  • New tests cover persistence before the auth check and mint, inheritance across launches, "" clearing it, no inheritance across a workspace switch, the cached-config rejection, flag→configure forwarding for both claude and codex, and the pinned id in each generated config (state.json agent block, Claude settings, both Codex overlays, opencode plugin, MCP proxy argv, both routing hooks).

🥞 Stacked PR


This pull request and its description were written by Isaac.

Completes the feature: `ug claude --oauth-client-id <id>` (and `ug codex`, bare
`ug`, `ug configure`) signs in through a custom OAuth app and every agent it
configures keeps minting from that same app.

`configure_shared_state` persists the id *before* it authenticates, because the
login and the gateway probe that follow both resolve the client id from the
workspace entry. It follows the "None means inherit" convention already used by
`use_pat` and `fable_enabled`, so a launch re-run keeps the workspace's app
without repeating the flag; `--oauth-client-id ""` clears it and goes back to
the built-in `databricks-cli` app.

The generated configs *pin* the id rather than leaving it to run-time
resolution. An agent runs its token helper as a bare command line, so the app
has to be named in the command itself -- `state_oauth_client_id` reads it out of
the state the writers already hold, for Claude's `apiKeyHelper`, Codex's
`auth.args` (both current and legacy overlays), opencode's auth plugin, the
registered `ucode mcp-proxy` argv, and both smart-routing hooks, which re-mint
in their own processes.

`_can_launch_from_cached_config` rejects an `--oauth-client-id` that disagrees
with what the cached config was written against; reusing it would launch the
agent with a token helper pointing at the other OAuth app.

`doctor.py` and `smart_routing/v2.py` need no change: the former's
`has_valid_databricks_auth` resolves the id from saved state by host, and the
latter only reads the helper's executable path.

Co-authored-by: Isaac <no-reply@databricks.com>
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