pydantic-claude-code
Use your Claude Code subscription from a plain pydantic-ai Agent or from
CLAI2,
with full pydantic-ai tool support. No API key, no separate billing: if Claude
Code works from your terminal, this works too.
The repo is mpfaffenberger/pydantic-ai-claude-code and the import is
pydantic_ai_claude_code; the PyPI project is pydantic-claude-code
(pip install pydantic-claude-code).
Models
| Model | ID |
|---|---|
| Claude Opus 5.5 | claude-opus-5-5 |
| Claude Sonnet 5.5 | claude-sonnet-5-5 |
| Claude Fable 5.1 | claude-fable-5-1 |
| Claude Haiku 4.5 | claude-haiku-4-5 |
These are the current models, the ones CLAI2's menus offer (config.MODELS).
They were checked on 2026-09-30 against Anthropic's
models overview,
against the anthropic SDK's model list that pydantic-ai's KnownModelName is
built from, and live against a Claude subscription. Any other model ID your
subscription serves, such as claude-opus-4-8, works too; it just isn't listed.
Use it in CLAI2
The plugin adds claude-code:MODEL models to CLAI2, next to its own providers,
so the stock agent, Coder, and your other plugins all keep working.
Install
One line installs the latest release of the plugin, and running it again updates it:
curl -fsSL https://raw.githubusercontent.com/mpfaffenberger/pydantic-ai-claude-code/main/install.sh | sh
To pin a release, pass its tag (... | sh -s -- v0.6.0), or pass main for
unreleased changes. The script downloads that version of this repo and copies the src/pydantic_ai_claude_code folder into CLAI2's
plugins folder as claude_code, replacing any older copy. Nothing to
pip install: everything the plugin imports (pydantic-ai with Anthropic
support, httpx2, keyring) already ships with CLAI2.
The plugins folder is $XDG_CONFIG_HOME/pydantic-clai2/plugins/, which is
~/.config/pydantic-clai2/plugins/ by default on macOS and Linux: the folder
next to CLAI2's config.db. To install by hand, copy the folder there yourself:
git clone --depth 1 https://github.com/mpfaffenberger/pydantic-ai-claude-code /tmp/claude-code-plugin
mkdir -p ~/.config/pydantic-clai2/plugins
cp -R /tmp/claude-code-plugin/src/pydantic_ai_claude_code ~/.config/pydantic-clai2/plugins/claude_code
To hack on the plugin, symlink the folder instead of copying it:
ln -s "$PWD/src/pydantic_ai_claude_code" ~/.config/pydantic-clai2/plugins/claude_code.
Dropped-in plugins load when CLAI2 starts. Inside CLAI2, /plugins lists it as
claude_code, where Space turns it off and on. (The shell's clai2 plugins list
shows only plugins added by name, so it won't appear there.)
Updates: each time CLAI2 starts, the plugin checks PyPI in the background.
When a newer release is out, it says so once, after your first reply, with the
one-liner to update. Being offline is silent. Set CLAUDE_CODE_NO_UPDATE_CHECK=1
to turn the check off.
CLAI2 version: the plugin is a class-based CLAI2 Plugin
(pydantic/pydantic-ai#9493,
merged), so it needs a CLAI2 built from pydantic-ai main at or after that
commit; no pydantic-clai2 release has it yet (0.52.0 is the latest). Plugin
0.5.0 was the last activate(host) plugin, for CLAI2 main before #9493. Until
a release is out, run CLAI2 from pydantic-ai's main:
git clone https://github.com/pydantic/pydantic-ai
cd pydantic-ai
uv run clai2
Or install it as a package instead, into CLAI2's environment, and point CLAI2
at it: uv tool install pydantic-clai2 --with pydantic-claude-code, then
/plugins add claude_code pydantic_ai_claude_code.
Sign in
Run /login claude. Your browser opens Claude's sign-in page, and CLAI2
prints the URL in case it doesn't. Or open the settings menu with /claude_code
(or C on the plugin in /plugins), choose Sign-in, and press Enter.
Pick a model
/login claude adds every model in the table above to your model list, so
/model offers them right away, and /model_settings sets thinking mode,
effort, and the rest for each. On an older CLAI2, or for a model that isn't
listed, open /add_model and choose the claude-code provider, or type one
directly:
/add_model claude-code:claude-opus-5-5
/model claude-code:claude-sonnet-5-5
Headless runs work the same way:
clai2 -p "Summarize this repo" -m claude-code:claude-fable-5-1.
Settings menu
/plugins configure claude_code, C in /plugins, or a bare /claude_code
opens it. Every change is saved right away and applies from the next run.
| Row | What it does | Stored in |
|---|---|---|
| Sign-in | Enter signs in through the browser; R signs out | the token store below, never in plugin settings |
| Credential storage | auto (CLAUDE_CODE_CREDENTIALS, else keyring, else a file), keyring, or file |
plugin settings (credentials) |
/claude_code logout and /claude_code status do the
same without the menu.
Where the sign-in lives
The sign-in is an OAuth token pair, not an API key, so it does not go in
/keys. It is kept where this package keeps it outside CLAI2, which means one
sign-in serves both CLAI2 and your own agents: the OS keyring (service
pydantic-ai-claude-code), or, without a keychain, a file readable only by
you. CLAI2's plugin settings are plaintext SQLite, so they hold only the
storage choice. Tokens are refreshed automatically and the refreshed pair is
saved back. The Credential storage row's auto follows the
CLAUDE_CODE_CREDENTIALS variable described below when it is set; keyring
and file override it.
Signing out deletes the stored tokens; they stay valid at Anthropic until they expire. Switching storage does not move an existing sign-in, so sign in again after switching.
If a run says Sign in to Claude Code first or Your Claude Code sign-in has expired, run /login claude. If the
plugin fails to load with cannot import name 'Plugin' from 'pydantic_clai2.plugins', your CLAI2 predates class-based plugins: upgrade it as described under
Install, or pin plugin 0.5.0.
Use it from Python
pydantic-ai owns the loop: its Agent drives the conversation, runs your
tools, and validates structured output. This package is just a model plus a
provider. Instead of a claude-code: model string, construct
ClaudeCodeModel('claude-fable-5-1') directly; it connects itself to a
ClaudeCodeProvider by default.
Quick start
import asyncio
from pydantic_ai import Agent
from pydantic_ai_claude_code import ClaudeCodeModel, default_store, login
async def main() -> None:
# One-time: opens your browser, mints tokens, stores them
# (it only needs to run again when the tokens are revoked).
if default_store().load() is None:
await login()
agent = Agent(ClaudeCodeModel('claude-opus-5-5'))
result = await agent.run('Say hi in three words.')
print(result.output)
asyncio.run(main())
You can also sign in from the shell: python -m pydantic_ai_claude_code login.
With tools
from pydantic_ai import Agent
from pydantic_ai_claude_code import ClaudeCodeModel
agent = Agent(ClaudeCodeModel('claude-sonnet-5-5'))
@agent.tool_plain
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
Tools defined on the agent are sent to the API as standard Anthropic tool
definitions. Structured output works the same way as with the built-in
anthropic provider.
Structured output
from pydantic import BaseModel
from pydantic_ai import Agent
from pydantic_ai_claude_code import ClaudeCodeModel
class Weather(BaseModel):
city: str
temperature_c: float
agent = Agent(ClaudeCodeModel('claude-fable-5-1'), output_type=Weather)
result = await agent.run('Weather in Paris right now?')
assert result.output.city == 'Paris'
Streaming
from pydantic_ai import Agent
from pydantic_ai_claude_code import ClaudeCodeModel
agent = Agent(ClaudeCodeModel('claude-haiku-4-5'))
async with agent.run_stream('Count from 1 to 3.') as stream:
async for chunk in stream.stream_text():
print(chunk, end='', flush=True)
How auth works
The flow uses the same shared public OAuth client that the Claude Code CLI uses:
- Authorization URL:
https://claude.ai/oauth/authorize - Token URL:
https://platform.claude.com/v1/oauth/token - Scopes:
org:create_api_key user:profile user:inference
Tokens are saved to the OS keyring by default: the Keychain on macOS,
Credential Manager on Windows, and Secret Service on Linux. On machines without
a keychain (including headless Linux, where keyring reports its fail
backend), credentials go to a JSON file instead, created 0600. You can
override its path with CLAUDE_CODE_AUTH_FILE:
~/.local/share/pydantic-ai-claude-code/auth.json
The file only ever contains what the issuer gave us. We never read the CLI's own credential files.
Force a backend with the CLAUDE_CODE_CREDENTIALS env var (keyring or
file), or pass one to default_store('file').
Refreshes happen automatically: the auth shim refreshes before expiry and
retries once on a 401, exactly like pydantic-ai's Codex provider. When the
refresh token itself is rejected, runs raise ClaudeCodeSignInExpiredError
(a UserError) telling you to sign in again.
Requests identify as Claude Code 2.1.285. The persona
"You are Claude Code, Anthropic's official CLI for Claude." is prepended to
the system context (position 0), as the CLI sends it. The client version
matters: the subscription backend refuses newer models to older CLI versions
(Opus 5.5 needs 2.1.280 or newer).
Security and scope
This is a plain Anthropic Messages API client, authenticated by your Claude subscription tokens. It does not run the Claude Code CLI in a subprocess, so it does not inherit Claude Code's sandboxing, permission prompts, or hooks. Treat it like any agent that runs code: only give it tools you trust.
Projects using this are responsible for following Anthropic's rules for using Claude Code credentials in their own products.
Development
uv sync --extra dev --extra clai2
uv run ruff check src tests && uv run ruff format --check src tests
uv run pyright
uv run pytest
To release, bump version in pyproject.toml and __version__ in
src/pydantic_ai_claude_code/__init__.py (a test checks they match), merge, and
push a matching tag (git tag v0.6.0 && git push origin v0.6.0). The Publish workflow tests,
builds, and uploads it to PyPI with the PYPI_API_TOKEN repository secret.
The tests never touch your real keychain or token file. They run against a local Messages API stub, including a full CLAI2 turn through the plugin.
Prior art
The OAuth mechanics (shared client ID, PKCE, token storage and refresh) are
lifted from the claude_code_oauth plugin in
code_puppy_core_plugins,
cleaned up and reshaped around the provider pattern in pydantic-ai.
Metadata
Release files for pydantic-claude-code 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pydantic_claude_code-0.6.0.tar.gz | 145.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pydantic_claude_code-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 172.0 kB
Release files / pydantic_claude_code-0.6.0.tar.gz
| Download URL | pydantic_claude_code-0.6.0.tar.gz |
|---|---|
| Size | 145.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0a81f3c4156940e65efa17007946e320d12e504c8ad77ff19da4d3d8edc2a0dc
|
|
BLAKE2b-256 checksum How to use checksums |
ad0aebdf24f0af33516fec897f502c361f7c9cdc59ebc8aa20b6d0ab8209d14e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / pydantic_claude_code-0.6.0-py3-none-any.whl
| Download URL | pydantic_claude_code-0.6.0-py3-none-any.whl |
|---|---|
| Size | 26.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e9cb53914ce0df1abc607af2e3e69637848b8d65bb1d6bcf9b442e5f1efac4d7
|
|
BLAKE2b-256 checksum How to use checksums |
1548b356ca759fc3a6197949fb08c7ff8fdc696b5e309e23e3c3c8305a01eded
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|