Skip to content

Commit 815b238

Browse files
committed
refactor(auth): store auth profiles in sqlite
1 parent c96a12d commit 815b238

87 files changed

Lines changed: 2821 additions & 4838 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/concepts/model-failover.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ These notices are operational messages, not assistant content. They are delivere
108108

109109
OpenClaw uses **auth profiles** for both API keys and OAuth tokens.
110110

111-
- Secrets live in `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` (legacy: `~/.openclaw/agent/auth-profiles.json`).
112-
- Runtime auth-routing state lives in `~/.openclaw/agents/<agentId>/agent/auth-state.json`.
111+
- Secrets and runtime auth-routing state live in `~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite`.
113112
- Config `auth.profiles` / `auth.order` are **metadata + routing only** (no secrets).
114-
- Legacy import-only OAuth file: `~/.openclaw/credentials/oauth.json` (imported into `auth-profiles.json` on first use).
113+
- Legacy import-only OAuth file: `~/.openclaw/credentials/oauth.json` (imported into the per-agent auth store on first use).
114+
- Legacy `auth-profiles.json`, `auth-state.json`, and per-agent `auth.json` files are imported by `openclaw doctor --fix`.
115115

116116
More detail: [OAuth](/concepts/oauth)
117117

@@ -127,7 +127,7 @@ OAuth logins create distinct profiles so multiple accounts can coexist.
127127
- Default: `provider:default` when no email is available.
128128
- OAuth with email: `provider:<email>` (for example `google-antigravity:user@gmail.com`).
129129

130-
Profiles live in `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` under `profiles`.
130+
Profiles live in the per-agent `openclaw-agent.sqlite` auth profile store.
131131

132132
## Rotation order
133133

@@ -141,7 +141,7 @@ When a provider has multiple profiles, OpenClaw chooses an order like this:
141141
`auth.profiles` filtered by provider.
142142
</Step>
143143
<Step title="Stored profiles">
144-
Entries in `auth-profiles.json` for the provider.
144+
Per-agent SQLite auth profile entries for the provider.
145145
</Step>
146146
</Steps>
147147

@@ -229,7 +229,7 @@ Cooldowns use exponential backoff:
229229
- 25 minutes
230230
- 1 hour (cap)
231231

232-
State is stored in `auth-state.json` under `usageStats`:
232+
State is stored in the per-agent SQLite auth state under `usageStats`:
233233

234234
```json
235235
{
@@ -253,7 +253,7 @@ Not every billing-shaped response is `402`, and not every HTTP `402` lands here.
253253
Meanwhile temporary `402` usage-window and organization/workspace spend-limit errors are classified as `rate_limit` when the message looks retryable (for example `weekly usage limit exhausted`, `daily limit reached, resets tomorrow`, or `organization spending limit exceeded`). Those stay on the short cooldown/failover path instead of the long billing-disable path.
254254
</Note>
255255

256-
State is stored in `auth-state.json`:
256+
State is stored in the per-agent SQLite auth state:
257257

258258
```json
259259
{

docs/gateway/authentication.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ This is a two-step setup:
8787
If `claude` is not on `PATH`, either install Claude Code first or set
8888
`agents.defaults.cliBackends.claude-cli.command` to the real binary path.
8989

90-
Manual token entry (any provider; writes `auth-profiles.json` + updates config):
90+
Manual token entry (any provider; writes the per-agent SQLite auth store + updates config):
9191

9292
```bash
9393
openclaw models auth paste-token --provider openrouter
9494
```
9595

96-
`auth-profiles.json` stores credentials only. The canonical shape is:
96+
The auth profile store keeps credentials only. Legacy `auth-profiles.json` files used this canonical shape:
9797

9898
```json
9999
{
@@ -108,9 +108,9 @@ openclaw models auth paste-token --provider openrouter
108108
}
109109
```
110110

111-
OpenClaw expects the canonical `version` + `profiles` shape at runtime. If an older install still has a flat file such as `{ "openrouter": { "apiKey": "..." } }`, run `openclaw doctor --fix` to rewrite it as an `openrouter:default` API-key profile; doctor keeps a `.legacy-flat.*.bak` copy beside the original. Endpoint details such as `baseUrl`, `api`, model ids, headers, and timeouts belong under `models.providers.<id>` in `openclaw.json` or `models.json`, not in `auth-profiles.json`.
111+
OpenClaw now reads auth profiles from each agent's `openclaw-agent.sqlite`. If an older install still has `auth-profiles.json`, `auth-state.json`, or a flat auth profile file such as `{ "openrouter": { "apiKey": "..." } }`, run `openclaw doctor --fix` to import it into SQLite; doctor keeps timestamped backups beside the original JSON files. Endpoint details such as `baseUrl`, `api`, model ids, headers, and timeouts belong under `models.providers.<id>` in `openclaw.json` or `models.json`, not in auth profiles.
112112

113-
External auth routes such as Bedrock `auth: "aws-sdk"` are also not credentials. If you want a named Bedrock route, put `auth.profiles.<id>.mode: "aws-sdk"` in `openclaw.json`; do not write `type: "aws-sdk"` into `auth-profiles.json`. `openclaw doctor --fix` moves legacy AWS SDK markers from the credential store into config metadata.
113+
External auth routes such as Bedrock `auth: "aws-sdk"` are also not credentials. If you want a named Bedrock route, put `auth.profiles.<id>.mode: "aws-sdk"` in `openclaw.json`; do not write `type: "aws-sdk"` into the auth profile store. `openclaw doctor --fix` moves legacy AWS SDK markers from the credential store into config metadata.
114114

115115
Auth profile refs are also supported for static credentials:
116116

@@ -225,7 +225,7 @@ Use `/model` (or `/model list`) for a compact picker; use `/model status` for th
225225

226226
### Per-agent (CLI override)
227227

228-
Set an explicit auth profile order override for an agent (stored in that agent's `auth-state.json`):
228+
Set an explicit auth profile order override for an agent (stored in that agent's SQLite auth state):
229229

230230
```bash
231231
openclaw models auth order get --provider anthropic

scripts/deadcode-unused-files.allowlist.mjs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ export const KNIP_UNUSED_FILE_ALLOWLIST = [
66
// The pending SQLite session/runtime branch wires these files into production.
77
"src/agents/cache/agent-cache-store.sqlite.ts",
88
"src/agents/cache/agent-cache-store.ts",
9-
"src/state/openclaw-agent-db.paths.ts",
10-
"src/state/openclaw-agent-db.ts",
11-
"src/state/openclaw-agent-schema.generated.ts",
129
];
1310

1411
// Knip can disagree across supported local/CI platforms for files that are
Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import fs from "node:fs";
21
import type { OpenClawConfig } from "../config/types.openclaw.js";
3-
import { tryReadJsonSync } from "../infra/json-files.js";
4-
import { replaceFileAtomicSync } from "../infra/replace-file.js";
5-
import { isRecord } from "../utils.js";
62
import type { AgentCredentialMap } from "./agent-auth-credentials.js";
73
import {
84
listProviderEnvAuthLookupKeys,
@@ -56,46 +52,3 @@ export function addEnvBackedAgentCredentials(
5652
}
5753
return next;
5854
}
59-
60-
export function scrubLegacyStaticAuthJsonEntriesForDiscovery(pathname: string): void {
61-
if (process.env.OPENCLAW_AUTH_STORE_READONLY === "1") {
62-
return;
63-
}
64-
if (!fs.existsSync(pathname)) {
65-
return;
66-
}
67-
68-
const parsed = tryReadJsonSync(pathname);
69-
if (!isRecord(parsed)) {
70-
return;
71-
}
72-
73-
let changed = false;
74-
for (const [provider, value] of Object.entries(parsed)) {
75-
if (!isRecord(value)) {
76-
continue;
77-
}
78-
if (value.type !== "api_key") {
79-
continue;
80-
}
81-
delete parsed[provider];
82-
changed = true;
83-
}
84-
85-
if (!changed) {
86-
return;
87-
}
88-
89-
if (Object.keys(parsed).length === 0) {
90-
fs.rmSync(pathname, { force: true });
91-
return;
92-
}
93-
94-
replaceFileAtomicSync({
95-
filePath: pathname,
96-
content: `${JSON.stringify(parsed, null, 2)}\n`,
97-
dirMode: 0o700,
98-
mode: 0o600,
99-
tempPrefix: ".agent-auth",
100-
});
101-
}

src/agents/agent-auth-discovery.external-cli.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const credentialMocks = vi.hoisted(() => ({
1515

1616
const discoveryCoreMocks = vi.hoisted(() => ({
1717
addEnvBackedAgentCredentials: vi.fn((credentials: unknown) => credentials),
18-
scrubLegacyStaticAuthJsonEntriesForDiscovery: vi.fn(),
1918
}));
2019

2120
const syntheticAuthMocks = vi.hoisted(() => ({

src/agents/agent-auth-discovery.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,4 @@ export function resolveAgentCredentialsForDiscovery(
8282
return credentials;
8383
}
8484

85-
export {
86-
addEnvBackedAgentCredentials,
87-
scrubLegacyStaticAuthJsonEntriesForDiscovery,
88-
} from "./agent-auth-discovery-core.js";
85+
export { addEnvBackedAgentCredentials } from "./agent-auth-discovery-core.js";

0 commit comments

Comments
 (0)