fix(auth/session): preserve override reset behavior and repair oauth profile-id drift (openclaw#18820) thanks @Glucksberg

Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Glucksberg <80581902+Glucksberg@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Glucksberg
2026-02-19 23:16:26 -04:00
committed by GitHub
parent f1e1cc4ee3
commit 38b4fb5d55
16 changed files with 376 additions and 46 deletions

View File

@@ -68,11 +68,7 @@ export async function applyAuthChoiceOAuth(
});
spin.stop("Chutes OAuth complete");
const email =
typeof creds.email === "string" && creds.email.trim() ? creds.email.trim() : "default";
const profileId = `chutes:${email}`;
await writeOAuthCredentials("chutes", creds, params.agentDir);
const profileId = await writeOAuthCredentials("chutes", creds, params.agentDir);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId,
provider: "chutes",

View File

@@ -117,9 +117,9 @@ export async function applyAuthChoiceOpenAI(
return { config: nextConfig, agentModelOverride };
}
if (creds) {
await writeOAuthCredentials("openai-codex", creds, params.agentDir);
const profileId = await writeOAuthCredentials("openai-codex", creds, params.agentDir);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "openai-codex:default",
profileId,
provider: "openai-codex",
mode: "oauth",
});

View File

@@ -1,4 +1,5 @@
import fs from "node:fs/promises";
import type { OAuthCredentials } from "@mariozechner/pi-ai";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { WizardPrompter } from "../wizard/prompts.js";
import { applyAuthChoice, resolvePreferredProviderForAuthChoice } from "./auth-choice.js";
@@ -22,7 +23,9 @@ vi.mock("../providers/github-copilot-auth.js", () => ({
githubCopilotLoginCommand: vi.fn(async () => {}),
}));
const loginOpenAICodexOAuth = vi.hoisted(() => vi.fn(async () => null));
const loginOpenAICodexOAuth = vi.hoisted(() =>
vi.fn<() => Promise<OAuthCredentials | null>>(async () => null),
);
vi.mock("./openai-codex-oauth.js", () => ({
loginOpenAICodexOAuth,
}));
@@ -123,6 +126,41 @@ describe("applyAuthChoice", () => {
).resolves.toEqual({ config: {} });
});
it("stores openai-codex OAuth with email profile id", async () => {
await setupTempState();
loginOpenAICodexOAuth.mockResolvedValueOnce({
email: "user@example.com",
refresh: "refresh-token",
access: "access-token",
expires: Date.now() + 60_000,
});
const prompter = createPrompter({});
const runtime = createExitThrowingRuntime();
const result = await applyAuthChoice({
authChoice: "openai-codex",
config: {},
prompter,
runtime,
setDefaultModel: false,
});
expect(result.config.auth?.profiles?.["openai-codex:user@example.com"]).toMatchObject({
provider: "openai-codex",
mode: "oauth",
});
expect(result.config.auth?.profiles?.["openai-codex:default"]).toBeUndefined();
expect(await readAuthProfile("openai-codex:user@example.com")).toMatchObject({
type: "oauth",
provider: "openai-codex",
refresh: "refresh-token",
access: "access-token",
email: "user@example.com",
});
});
it("prompts and writes MiniMax API key when selecting minimax-api", async () => {
await setupTempState();

View File

@@ -10,11 +10,12 @@ export async function writeOAuthCredentials(
provider: string,
creds: OAuthCredentials,
agentDir?: string,
): Promise<void> {
): Promise<string> {
const email =
typeof creds.email === "string" && creds.email.trim() ? creds.email.trim() : "default";
const profileId = `${provider}:${email}`;
upsertAuthProfile({
profileId: `${provider}:${email}`,
profileId,
credential: {
type: "oauth",
provider,
@@ -22,6 +23,7 @@ export async function writeOAuthCredentials(
},
agentDir: resolveAuthAgentDir(agentDir),
});
return profileId;
}
export async function setAnthropicApiKey(key: string, agentDir?: string) {

View File

@@ -125,12 +125,13 @@ describe("writeOAuthCredentials", () => {
expires: Date.now() + 60_000,
} satisfies OAuthCredentials;
await writeOAuthCredentials("openai-codex", creds);
const profileId = await writeOAuthCredentials("openai-codex", creds);
expect(profileId).toBe("openai-codex:default");
const parsed = await readAuthProfilesForAgent<{
profiles?: Record<string, OAuthCredentials & { type?: string }>;
}>(env.agentDir);
expect(parsed.profiles?.["openai-codex:default"]).toMatchObject({
expect(parsed.profiles?.[profileId]).toMatchObject({
refresh: "refresh-token",
access: "access-token",
type: "oauth",
@@ -140,6 +141,32 @@ describe("writeOAuthCredentials", () => {
fs.readFile(path.join(env.stateDir, "agents", "main", "agent", "auth-profiles.json"), "utf8"),
).rejects.toThrow();
});
it("uses OAuth email as profile id when provided", async () => {
const env = await setupAuthTestEnv("openclaw-oauth-");
lifecycle.setStateDir(env.stateDir);
const creds = {
email: "user@example.com",
refresh: "refresh-token",
access: "access-token",
expires: Date.now() + 60_000,
} satisfies OAuthCredentials;
const profileId = await writeOAuthCredentials("openai-codex", creds);
expect(profileId).toBe("openai-codex:user@example.com");
const parsed = await readAuthProfilesForAgent<{
profiles?: Record<string, OAuthCredentials & { type?: string }>;
}>(env.agentDir);
expect(parsed.profiles?.[profileId]).toMatchObject({
refresh: "refresh-token",
access: "access-token",
type: "oauth",
provider: "openai-codex",
email: "user@example.com",
});
});
});
describe("setMinimaxApiKey", () => {