Skip to content

Commit 30a5541

Browse files
chore: split CLAUDE.md by domain for lazy context loading
Move domain-specific rules from the root CLAUDE.md into nested CLAUDE.md files so Claude Code only loads them when working in the relevant subdirectory (per the documented hierarchical memory behavior). Root keeps universal rules (commands, architecture, style, testing). No rule wording changed. Also adds empty stubs for proxy sub-domains (auth, guardrails, hooks, spend_tracking, pass_through_endpoints) so future rules land in the right place.
1 parent 850fe59 commit 30a5541

File tree

11 files changed

+120
-58
lines changed

11 files changed

+120
-58
lines changed

.circleci/CLAUDE.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# CI (`.circleci/`)
2+
3+
CircleCI pipeline configuration.
4+
5+
## CI Supply-Chain Safety
6+
- **Never pipe a remote script into a shell** (`curl ... | bash`, `wget ... | sh`). Download the artifact to a file, verify its SHA-256 checksum, then install.
7+
- **Pin every external tool to a specific version** with a full URL (not `latest` or `stable`). Unversioned downloads silently change under you.
8+
- **Verify checksums for all downloaded binaries.** Use the provider's official `.sha256` / `.sha256sum` sidecar file when available; otherwise compute and hardcode the digest.
9+
- **Prefer reusable CircleCI commands** (`commands:` section) so a tool is installed and verified in exactly one place, then referenced everywhere with `- install_<tool>` or `- wait_for_service`.
10+
- **Don't add tools just because they were there before.** Audit whether an external dependency is still needed. If it can be replaced with a shell one-liner or a tool already in the image, remove it.
11+
- These rules apply to every download in CI: binaries, install scripts, language version managers, package repos. No exceptions.

CLAUDE.md

Lines changed: 19 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44

5+
## Nested CLAUDE.md files
6+
7+
Domain-specific rules live next to the code they apply to. They load automatically when Claude reads files in those directories:
8+
9+
- `ui/litellm-dashboard/CLAUDE.md` — UI dashboard (antd, browser storage, UI↔backend contracts)
10+
- `litellm/proxy/CLAUDE.md` — proxy server (DB access, migrations, schema sync)
11+
- `litellm/proxy/management_endpoints/CLAUDE.md` — admin CRUD endpoints (MCP credential storage, MCP OAuth backend)
12+
- `litellm/proxy/auth/CLAUDE.md` — auth (stub)
13+
- `litellm/proxy/guardrails/CLAUDE.md` — guardrails (stub)
14+
- `litellm/proxy/hooks/CLAUDE.md` — proxy hooks (stub)
15+
- `litellm/proxy/spend_tracking/CLAUDE.md` — spend tracking (stub)
16+
- `litellm/proxy/pass_through_endpoints/CLAUDE.md` — pass-through endpoints (stub)
17+
- `litellm/caching/CLAUDE.md` — caching (HTTP client cache safety)
18+
- `.circleci/CLAUDE.md` — CI supply-chain rules
19+
520
## Development Commands
621

722
### Installation
@@ -106,44 +121,6 @@ LiteLLM is a unified interface for 100+ LLM providers with two main components:
106121
- **Keep monkeypatch stubs in sync with real signatures** — when a function gains a new optional parameter, update every `fake_*` / `stub_*` in tests that patch it to also accept that kwarg (even as `**kwargs`). Stale stubs fail with `unexpected keyword argument` and mask real bugs.
107122
- **Test all branches of name→ID resolution** — when adding server/resource lookup that resolves names to UUIDs, test: (1) name resolves and UUID is allowed, (2) name resolves but UUID is not allowed, (3) name does not resolve at all. The silent-fallback path is where access-control bugs hide.
108123

109-
### UI / Backend Consistency
110-
- When wiring a new UI entity type to an existing backend endpoint, verify the backend API contract (single value vs. array, required vs. optional params) and ensure the UI controls match — e.g., use a single-select dropdown when the backend accepts a single value, not a multi-select
111-
112-
### UI Component Library
113-
- **Always use `antd` for new UI components** — we are migrating off of `@tremor/react`. Do not introduce new `Badge`, `Text`, `Card`, `Grid`, `Title`, or other imports from `@tremor/react` in any new or modified file. Use `antd` equivalents: `Tag` for labels, plain `<span>`/`<div>` with Tailwind classes (or `Typography.Text`) for text, `Card` from `antd`, etc. Note that `antd` has no `"yellow"` Tag color — use `"gold"` for amber/yellow.
114-
115-
### MCP OAuth / OpenAPI Transport Mapping
116-
- `TRANSPORT.OPENAPI` is a UI-only concept. The backend only accepts `"http"`, `"sse"`, or `"stdio"`. Always map it to `"http"` before any API call (including pre-OAuth temp-session calls).
117-
- FastAPI validation errors return `detail` as an array of `{loc, msg, type}` objects. Error extractors must handle: array (map `.msg`), string, nested `{error: string}`, and fallback.
118-
- When an MCP server already has `authorization_url` stored, skip OAuth discovery (`_discovery_metadata`) — the server URL for OpenAPI MCPs is the spec file, not the API base, and fetching it causes timeouts.
119-
- `client_id` should be optional in the `/authorize` endpoint — if the server has a stored `client_id` in credentials, use that. Never require callers to re-supply it.
120-
121-
### MCP Credential Storage
122-
- OAuth credentials and BYOK credentials share the `litellm_mcpusercredentials` table, distinguished by a `"type"` field in the JSON payload (`"oauth2"` vs plain string).
123-
- When deleting OAuth credentials, check type before deleting to avoid accidentally deleting a BYOK credential for the same `(user_id, server_id)` pair.
124-
- Always pass the raw `expires_at` timestamp to the client — never set it to `None` for expired credentials. Let the frontend compute the "Expired" display state from the timestamp.
125-
- Use `RecordNotFoundError` (not bare `except Exception`) when catching "already deleted" in credential delete endpoints.
126-
127-
### Browser Storage Safety (UI)
128-
- Never write LiteLLM access tokens or API keys to `localStorage` — use `sessionStorage` only. `localStorage` survives browser close and is readable by any injected script (XSS).
129-
- Shared utility functions (e.g. `extractErrorMessage`) belong in `src/utils/` — never define them inline in hooks or duplicate them across files.
130-
131-
### Database Migrations
132-
- Prisma handles schema migrations
133-
- Migration files auto-generated with `prisma migrate dev`
134-
- Always test migrations against both PostgreSQL and SQLite
135-
136-
### Proxy database access
137-
- **Do not write raw SQL** for proxy DB operations. Use Prisma model methods instead of `execute_raw` / `query_raw`.
138-
- Use the generated client: `prisma_client.db.<model>` (e.g. `litellm_tooltable`, `litellm_usertable`) with `.upsert()`, `.find_many()`, `.find_unique()`, `.update()`, `.update_many()` as appropriate. This avoids schema/client drift, keeps code testable with simple mocks, and matches patterns used in spend logs and other proxy code.
139-
- **No N+1 queries.** Never query the DB inside a loop. Batch-fetch with `{"in": ids}` and distribute in-memory.
140-
- **Batch writes.** Use `create_many`/`update_many`/`delete_many` instead of individual calls (these return counts only; `update_many`/`delete_many` no-op silently on missing rows). When multiple separate writes target the same table (e.g. in `batch_()`), order by primary key to avoid deadlocks.
141-
- **Push work to the DB.** Filter, sort, group, and aggregate in SQL, not Python. Verify Prisma generates the expected SQL — e.g. prefer `group_by` over `find_many(distinct=...)` which does client-side processing.
142-
- **Bound large result sets.** Prisma materializes full results in memory. For results over ~10 MB, paginate with `take`/`skip` or `cursor`/`take`, always with an explicit `order`. Prefer cursor-based pagination (`skip` is O(n)). Don't paginate naturally small result sets.
143-
- **Limit fetched columns on wide tables.** Use `select` to fetch only needed fields — returns a partial object, so downstream code must not access unselected fields.
144-
- **Check index coverage.** For new or modified queries, check `schema.prisma` for a supporting index. Prefer extending an existing index (e.g. `@@index([a])``@@index([a, b])`) over adding a new one, unless it's a `@@unique`. Only add indexes for large/frequent queries.
145-
- **Keep schema files in sync.** Apply schema changes to all `schema.prisma` copies (`schema.prisma`, `litellm/proxy/`, `litellm-proxy-extras/`, `litellm-js/spend-logs/` for SpendLogs) with a migration under `litellm-proxy-extras/litellm_proxy_extras/migrations/`.
146-
147124
### Setup Wizard (`litellm/setup_wizard.py`)
148125
- The wizard is implemented as a single `SetupWizard` class with `@staticmethod` methods — keep it that way. No module-level functions except `run_setup_wizard()` (the public entrypoint) and pure helpers (color, ANSI).
149126
- Use `litellm.utils.check_valid_key(model, api_key)` for credential validation — never roll a custom completion call.
@@ -154,23 +131,7 @@ LiteLLM is a unified interface for 100+ LLM providers with two main components:
154131
- Optional features enabled via environment variables
155132
- Separate licensing and authentication for enterprise features
156133

157-
### CI Supply-Chain Safety
158-
- **Never pipe a remote script into a shell** (`curl ... | bash`, `wget ... | sh`). Download the artifact to a file, verify its SHA-256 checksum, then install.
159-
- **Pin every external tool to a specific version** with a full URL (not `latest` or `stable`). Unversioned downloads silently change under you.
160-
- **Verify checksums for all downloaded binaries.** Use the provider's official `.sha256` / `.sha256sum` sidecar file when available; otherwise compute and hardcode the digest.
161-
- **Prefer reusable CircleCI commands** (`commands:` section) so a tool is installed and verified in exactly one place, then referenced everywhere with `- install_<tool>` or `- wait_for_service`.
162-
- **Don't add tools just because they were there before.** Audit whether an external dependency is still needed. If it can be replaced with a shell one-liner or a tool already in the image, remove it.
163-
- These rules apply to every download in CI: binaries, install scripts, language version managers, package repos. No exceptions.
164-
165-
### HTTP Client Cache Safety
166-
- **Never close HTTP/SDK clients on cache eviction.** `LLMClientCache._remove_key()` must not call `close()`/`aclose()` on evicted clients — they may still be used by in-flight requests. Doing so causes `RuntimeError: Cannot send a request, as the client has been closed.` after the 1-hour TTL expires. Cleanup happens at shutdown via `close_litellm_async_clients()`.
167-
168-
### Troubleshooting: DB schema out of sync after proxy restart
169-
`litellm-proxy-extras` runs `prisma migrate deploy` on startup using **its own** bundled migration files, which may lag behind schema changes in the current worktree. Symptoms: `Unknown column`, `Invalid prisma invocation`, or missing data on new fields.
170-
171-
**Diagnose:** Run `\d "TableName"` in psql and compare against `schema.prisma` — missing columns confirm the issue.
172-
173-
**Fix options:**
174-
1. **Create a Prisma migration** (permanent) — run `prisma migrate dev --name <description>` in the worktree. The generated file will be picked up by `prisma migrate deploy` on next startup.
175-
2. **Apply manually for local dev**`psql -d litellm -c "ALTER TABLE ... ADD COLUMN IF NOT EXISTS ..."` after each proxy start. Fine for dev, not for production.
176-
3. **Update litellm-proxy-extras** — if the package is installed from PyPI, its migration directory must include the new file. Either update the package or run the migration manually until the next release ships it.
134+
### Database Migrations
135+
- Prisma handles schema migrations
136+
- Migration files auto-generated with `prisma migrate dev`
137+
- Always test migrations against both PostgreSQL and SQLite

litellm/caching/CLAUDE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Caching (`litellm/caching/`)
2+
3+
Multiple cache backends (Redis, in-memory, S3, disk) + HTTP client cache.
4+
5+
## HTTP Client Cache Safety
6+
- **Never close HTTP/SDK clients on cache eviction.** `LLMClientCache._remove_key()` must not call `close()`/`aclose()` on evicted clients — they may still be used by in-flight requests. Doing so causes `RuntimeError: Cannot send a request, as the client has been closed.` after the 1-hour TTL expires. Cleanup happens at shutdown via `close_litellm_async_clients()`.

litellm/proxy/CLAUDE.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Proxy Server (`litellm/proxy/`)
2+
3+
Rules that apply across the proxy server. Sub-domains (auth, guardrails, management endpoints, etc.) have their own nested CLAUDE.md files.
4+
5+
## Proxy database access
6+
- **Do not write raw SQL** for proxy DB operations. Use Prisma model methods instead of `execute_raw` / `query_raw`.
7+
- Use the generated client: `prisma_client.db.<model>` (e.g. `litellm_tooltable`, `litellm_usertable`) with `.upsert()`, `.find_many()`, `.find_unique()`, `.update()`, `.update_many()` as appropriate. This avoids schema/client drift, keeps code testable with simple mocks, and matches patterns used in spend logs and other proxy code.
8+
- **No N+1 queries.** Never query the DB inside a loop. Batch-fetch with `{"in": ids}` and distribute in-memory.
9+
- **Batch writes.** Use `create_many`/`update_many`/`delete_many` instead of individual calls (these return counts only; `update_many`/`delete_many` no-op silently on missing rows). When multiple separate writes target the same table (e.g. in `batch_()`), order by primary key to avoid deadlocks.
10+
- **Push work to the DB.** Filter, sort, group, and aggregate in SQL, not Python. Verify Prisma generates the expected SQL — e.g. prefer `group_by` over `find_many(distinct=...)` which does client-side processing.
11+
- **Bound large result sets.** Prisma materializes full results in memory. For results over ~10 MB, paginate with `take`/`skip` or `cursor`/`take`, always with an explicit `order`. Prefer cursor-based pagination (`skip` is O(n)). Don't paginate naturally small result sets.
12+
- **Limit fetched columns on wide tables.** Use `select` to fetch only needed fields — returns a partial object, so downstream code must not access unselected fields.
13+
- **Check index coverage.** For new or modified queries, check `schema.prisma` for a supporting index. Prefer extending an existing index (e.g. `@@index([a])``@@index([a, b])`) over adding a new one, unless it's a `@@unique`. Only add indexes for large/frequent queries.
14+
- **Keep schema files in sync.** Apply schema changes to all `schema.prisma` copies (`schema.prisma`, `litellm/proxy/`, `litellm-proxy-extras/`, `litellm-js/spend-logs/` for SpendLogs) with a migration under `litellm-proxy-extras/litellm_proxy_extras/migrations/`.
15+
16+
## Database Migrations
17+
- Prisma handles schema migrations
18+
- Migration files auto-generated with `prisma migrate dev`
19+
- Always test migrations against both PostgreSQL and SQLite
20+
21+
## Troubleshooting: DB schema out of sync after proxy restart
22+
`litellm-proxy-extras` runs `prisma migrate deploy` on startup using **its own** bundled migration files, which may lag behind schema changes in the current worktree. Symptoms: `Unknown column`, `Invalid prisma invocation`, or missing data on new fields.
23+
24+
**Diagnose:** Run `\d "TableName"` in psql and compare against `schema.prisma` — missing columns confirm the issue.
25+
26+
**Fix options:**
27+
1. **Create a Prisma migration** (permanent) — run `prisma migrate dev --name <description>` in the worktree. The generated file will be picked up by `prisma migrate deploy` on next startup.
28+
2. **Apply manually for local dev**`psql -d litellm -c "ALTER TABLE ... ADD COLUMN IF NOT EXISTS ..."` after each proxy start. Fine for dev, not for production.
29+
3. **Update litellm-proxy-extras** — if the package is installed from PyPI, its migration directory must include the new file. Either update the package or run the migration manually until the next release ships it.

litellm/proxy/auth/CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Auth (`litellm/proxy/auth/`)
2+
3+
API key management, JWT, OAuth2, SSO, virtual keys, team/org access control.
4+
5+
_No rules yet — add guidance here as it emerges from real issues._

litellm/proxy/guardrails/CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Guardrails (`litellm/proxy/guardrails/`)
2+
3+
Safety and content-filtering hooks, guardrail registry, guardrail initializers.
4+
5+
_No rules yet — add guidance here as it emerges from real issues._

litellm/proxy/hooks/CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Proxy Hooks (`litellm/proxy/hooks/`)
2+
3+
Pre-call / post-call hooks: alerting, parallel request limits, cache-control, etc.
4+
5+
_No rules yet — add guidance here as it emerges from real issues._
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Management Endpoints (`litellm/proxy/management_endpoints/`)
2+
3+
Admin CRUD endpoints: keys, teams, users, orgs, budgets, models, MCP servers, SCIM, SSO.
4+
5+
## MCP OAuth / OpenAPI (backend)
6+
- When an MCP server already has `authorization_url` stored, skip OAuth discovery (`_discovery_metadata`) — the server URL for OpenAPI MCPs is the spec file, not the API base, and fetching it causes timeouts.
7+
- `client_id` should be optional in the `/authorize` endpoint — if the server has a stored `client_id` in credentials, use that. Never require callers to re-supply it.
8+
9+
## MCP Credential Storage
10+
- OAuth credentials and BYOK credentials share the `litellm_mcpusercredentials` table, distinguished by a `"type"` field in the JSON payload (`"oauth2"` vs plain string).
11+
- When deleting OAuth credentials, check type before deleting to avoid accidentally deleting a BYOK credential for the same `(user_id, server_id)` pair.
12+
- Always pass the raw `expires_at` timestamp to the client — never set it to `None` for expired credentials. Let the frontend compute the "Expired" display state from the timestamp.
13+
- Use `RecordNotFoundError` (not bare `except Exception`) when catching "already deleted" in credential delete endpoints.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Pass-Through Endpoints (`litellm/proxy/pass_through_endpoints/`)
2+
3+
Provider-native request forwarding (not OpenAI-shaped).
4+
5+
_No rules yet — add guidance here as it emerges from real issues._
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Spend Tracking (`litellm/proxy/spend_tracking/`)
2+
3+
SpendLogs, daily activity aggregation, cost tracking.
4+
5+
_No rules yet — add guidance here as it emerges from real issues._

0 commit comments

Comments
 (0)