fix(agents): detect codex cli auth in status

Fixes #70688.
Co-authored-by: Jon Brown <801241+jb510@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-04-24 06:47:21 +01:00
parent a53fea3905
commit bae7b54a85
5 changed files with 63 additions and 0 deletions

View File

@@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Codex harness: route native `request_user_input` prompts back to the originating chat, preserve queued follow-up answers, and honor newer app-server command approval amendment decisions.
- Codex status: report Codex CLI OAuth as `oauth (codex-cli)` for native `codex/*` sessions instead of showing unknown auth. Fixes #70688. Thanks @jb510.
- Codex harness/context-engine: redact context-engine assembly failures before logging, so fallback warnings do not serialize raw error objects. (#70809) Thanks @jalehman.
- WhatsApp/onboarding: keep first-run setup entry loading off the Baileys runtime dependency path, so packaged QuickStart installs can show WhatsApp setup before runtime deps are staged. Fixes #70932.
- Block streaming: suppress final assembled text after partial block-delivery aborts when the already-sent text chunks exactly cover the final reply, preventing duplicate replies without dropping unrelated short messages. Fixes #70921.

View File

@@ -7,6 +7,7 @@ const mocks = vi.hoisted(() => ({
resolveAuthProfileDisplayLabel: vi.fn(),
resolveUsableCustomProviderApiKey: vi.fn(() => null),
resolveEnvApiKey: vi.fn(() => null),
readCodexCliCredentialsCached: vi.fn<() => unknown>(() => null),
}));
vi.mock("./auth-profiles.js", () => ({
@@ -21,6 +22,10 @@ vi.mock("./model-auth.js", () => ({
resolveEnvApiKey: mocks.resolveEnvApiKey,
}));
vi.mock("./cli-credentials.js", () => ({
readCodexCliCredentialsCached: mocks.readCodexCliCredentialsCached,
}));
let resolveModelAuthLabel: typeof import("./model-auth-label.js").resolveModelAuthLabel;
describe("resolveModelAuthLabel", () => {
@@ -36,6 +41,8 @@ describe("resolveModelAuthLabel", () => {
mocks.resolveUsableCustomProviderApiKey.mockReturnValue(null);
mocks.resolveEnvApiKey.mockReset();
mocks.resolveEnvApiKey.mockReturnValue(null);
mocks.readCodexCliCredentialsCached.mockReset();
mocks.readCodexCliCredentialsCached.mockReturnValue(null);
});
it("does not include token value in label for token profiles", () => {
@@ -112,6 +119,28 @@ describe("resolveModelAuthLabel", () => {
expect(label).toBe("oauth (anthropic:oauth)");
});
it("shows codex cli auth for codex provider without auth profiles", () => {
mocks.ensureAuthProfileStore.mockReturnValue({
version: 1,
profiles: {},
} as never);
mocks.resolveAuthProfileOrder.mockReturnValue([]);
mocks.readCodexCliCredentialsCached.mockReturnValue({
type: "oauth",
provider: "openai-codex",
access: "token",
refresh: "refresh",
expires: Date.now() + 60_000,
});
const label = resolveModelAuthLabel({
provider: "codex",
cfg: {},
});
expect(label).toBe("oauth (codex-cli)");
});
it("can skip external auth profile overlays for status labels", () => {
mocks.loadAuthProfileStoreWithoutExternalProfiles.mockReturnValue({
version: 1,

View File

@@ -6,6 +6,7 @@ import {
resolveAuthProfileDisplayLabel,
resolveAuthProfileOrder,
} from "./auth-profiles.js";
import { readCodexCliCredentialsCached } from "./cli-credentials.js";
import { resolveEnvApiKey, resolveUsableCustomProviderApiKey } from "./model-auth.js";
import { normalizeProviderId } from "./model-selection.js";
@@ -64,6 +65,10 @@ export function resolveModelAuthLabel(params: {
return `api-key (${envKey.source})`;
}
if (providerKey === "codex" && readCodexCliCredentialsCached({ ttlMs: 5_000 })) {
return "oauth (codex-cli)";
}
const customKey = resolveUsableCustomProviderApiKey({
cfg: params.cfg,
provider: providerKey,

View File

@@ -92,12 +92,14 @@ let resolveApiKeyForProvider: typeof import("./model-auth.js").resolveApiKeyForP
let resolveAwsSdkEnvVarName: typeof import("./model-auth.js").resolveAwsSdkEnvVarName;
let resolveModelAuthMode: typeof import("./model-auth.js").resolveModelAuthMode;
let resolveUsableCustomProviderApiKey: typeof import("./model-auth.js").resolveUsableCustomProviderApiKey;
let cliCredentials: typeof import("./cli-credentials.js");
let clearRuntimeConfigSnapshot: typeof import("../config/config.js").clearRuntimeConfigSnapshot;
let setRuntimeConfigSnapshot: typeof import("../config/config.js").setRuntimeConfigSnapshot;
beforeAll(async () => {
vi.resetModules();
({ clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } = await import("../config/config.js"));
cliCredentials = await import("./cli-credentials.js");
({
applyAuthHeaderOverride,
applyLocalNoAuthHeaderOverride,
@@ -259,6 +261,24 @@ describe("resolveModelAuthMode", () => {
"aws-sdk",
);
});
it("returns oauth for codex when Codex CLI auth is available", () => {
const readCodexCliCredentialsCached = vi
.spyOn(cliCredentials, "readCodexCliCredentialsCached")
.mockReturnValue({
type: "oauth",
provider: "openai-codex",
access: "token",
refresh: "refresh",
expires: Date.now() + 60_000,
});
try {
expect(resolveModelAuthMode("codex", undefined, { version: 1, profiles: {} })).toBe("oauth");
} finally {
readCodexCliCredentialsCached.mockRestore();
}
});
});
describe("requireApiKey", () => {

View File

@@ -27,6 +27,7 @@ import {
resolveAuthProfileOrder,
resolveAuthStorePathForDisplay,
} from "./auth-profiles.js";
import * as cliCredentials from "./cli-credentials.js";
import { resolveEnvApiKey, type EnvApiKeyResult } from "./model-auth-env.js";
import {
CUSTOM_LOCAL_AUTH_MARKER,
@@ -654,6 +655,13 @@ export function resolveModelAuthMode(
return envKey.source.includes("OAUTH_TOKEN") ? "oauth" : "api-key";
}
if (
normalizeProviderId(resolved) === "codex" &&
cliCredentials.readCodexCliCredentialsCached({ ttlMs: 5_000 })
) {
return "oauth";
}
if (hasUsableCustomProviderApiKey(cfg, resolved)) {
return "api-key";
}