mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-05 01:21:15 +00:00
fix(auth): stop Codex OAuth refresh spam
Treat high-confidence app-server OAuth refresh invalidation as terminal auth-profile failure, while keeping entitlement and rate-limit payloads out of re-auth classification.
This commit is contained in:
@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Canvas: return not found for malformed percent-encoded Canvas/A2UI/document asset paths and keep decoded parent traversal blocked before path normalization.
|
||||
- Agents: allow dot-dot-prefixed filenames such as `..note.txt` through sandbox FS bridge, remote sandbox reads, and apply_patch summaries without mistaking the name for parent traversal.
|
||||
- CLI/migrate: hide per-item source/plugin hints on non-conflicting Codex skill and plugin selection prompts, keeping the hint text reserved for rows that actually need attention. Thanks @sjf.
|
||||
- Codex harness: treat high-confidence app-server OAuth refresh invalidation as a terminal auth-profile failure, stopping repeated raw token-refresh errors without turning entitlement or usage-limit payloads into re-auth prompts.
|
||||
- CLI/migrate: humanize Codex conflict-status messaging across the migrate UI so selection prompts and plan/result rows say "Codex skill already installed in workspace" instead of surfacing internal `MIGRATION_REASON_*` codes. Thanks @sjf.
|
||||
- CLI/migrate: render migrate result rows with distinct glyphs for manual-review (🔍) and archive (📖) items instead of the misleading "skipped" and "migrated" checkmarks, so users can see which entries still need attention versus which were filed away. Thanks @sjf.
|
||||
- CLI/migrate: split Codex migrate output into separate preview and result phases so the Before plan and After result render through clack with independently tunable copy. Thanks @sjf.
|
||||
@@ -43,7 +44,7 @@ Docs: https://docs.openclaw.ai
|
||||
- macOS/companion: require system TLS trust before pinning a first-use direct `wss://` gateway certificate and honor `gateway.remote.tlsFingerprint` as the explicit pin for remote node-mode sessions, so fresh endpoints fail closed when macOS cannot trust the certificate unless configured out of band. Fixes #50642. Thanks @BunsDev.
|
||||
- Update: snapshot config before update-time repair and restart writes, preserve plugin install records through doctor cleanup, and keep update-time config size drops from blocking the update while pointing users to the pre-update backup. Fixes #80077. (#80257) Thanks @Jerry-Xin and @vincentkoc.
|
||||
- WebChat/TUI: route Codex `tools.message` source replies to the active internal UI turn and mirror them to session history, so message-tool-only harness replies, including rich presentation and button-only replies, no longer disappear while WebChat and TUI remain non-targetable outbound channels. (#81586) Thanks @pashpashpash.
|
||||
- Codex auth: accept OAuth profiles backed by `oauthRef` during runtime auth selection, so official Codex OAuth logins are used by app-server agent runs. (#81633)
|
||||
- Codex auth: accept OAuth profiles backed by `oauthRef` during runtime auth selection, so official Codex OAuth logins are used by app-server agent runs. (#81633) Thanks @obviyus.
|
||||
- Sessions/status: classify ACP spawn-child sessions as `kind: "spawn-child"` instead of `"direct"` in `openclaw sessions` and status output; extract the duplicated session-kind classifier into a shared helper (`src/sessions/classify-session-kind.ts`) so both surfaces stay in sync. Fixes catalog #19. (#79544)
|
||||
- Sessions/Gateway: report `agentRuntime.id: "acpx"` (or stored backend id) with `source: "session-key"` for ACP control-plane session rows in `openclaw sessions --json`, `openclaw status`, and Gateway session RPC responses instead of the incorrect `"auto"` / `"pi"` implicit fallback. Fixes catalog #18. (#79550)
|
||||
- Telegram: delete tool-progress-only draft bubbles before rotating to the real answer, preventing orphaned progress messages in streamed replies.
|
||||
|
||||
@@ -676,6 +676,41 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
).toBe("openai-codex:default");
|
||||
});
|
||||
|
||||
it("answers refresh requests from a discovered oauthRef-backed Codex profile", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
|
||||
oauthMocks.refreshOpenAICodexToken.mockResolvedValueOnce({
|
||||
access: "refreshed-ref-backed-access-token",
|
||||
refresh: "refreshed-ref-backed-refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
accountId: "account-ref-backed-refreshed",
|
||||
});
|
||||
try {
|
||||
upsertAuthProfile({
|
||||
agentDir,
|
||||
profileId: "openai-codex:default",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "openai-codex",
|
||||
access: "ref-backed-access-token",
|
||||
refresh: "ref-backed-refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
accountId: "account-ref-backed",
|
||||
email: "codex@example.test",
|
||||
},
|
||||
});
|
||||
|
||||
await expect(refreshCodexAppServerAuthTokens({ agentDir })).resolves.toEqual({
|
||||
accessToken: "refreshed-ref-backed-access-token",
|
||||
chatgptAccountId: "account-ref-backed-refreshed",
|
||||
chatgptPlanType: null,
|
||||
});
|
||||
|
||||
expect(oauthMocks.refreshOpenAICodexToken).toHaveBeenCalledWith("ref-backed-refresh-token");
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("applies native Codex CLI OAuth when no OpenClaw auth profile exists", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
|
||||
const agentDir = path.join(root, "agent");
|
||||
|
||||
@@ -1442,21 +1442,34 @@ describe("classifyProviderRuntimeFailureKind", () => {
|
||||
});
|
||||
|
||||
it("classifies OAuth refresh failures", () => {
|
||||
expect(
|
||||
classifyProviderRuntimeFailureKind(
|
||||
"OAuth token refresh failed for openai-codex: invalid_grant. Please try again or re-authenticate.",
|
||||
),
|
||||
).toBe("auth_refresh");
|
||||
expect(
|
||||
classifyProviderRuntimeFailureKind(
|
||||
"Your access token could not be refreshed because you have since logged out or signed in to another account. Please sign in again.",
|
||||
),
|
||||
).toBe("auth_refresh");
|
||||
expect(
|
||||
classifyProviderRuntimeFailureKind(
|
||||
"Your authentication session could not be refreshed automatically. Please log out and sign in again.",
|
||||
),
|
||||
).toBe("auth_refresh");
|
||||
const refreshFailures = [
|
||||
"OAuth token refresh failed for openai-codex: invalid_grant. Please try again or re-authenticate.",
|
||||
"Your access token could not be refreshed because you have since logged out or signed in to another account. Please sign in again.",
|
||||
"Your authentication session could not be refreshed automatically. Please log out and sign in again.",
|
||||
];
|
||||
for (const message of refreshFailures) {
|
||||
expect(classifyProviderRuntimeFailureKind(message)).toBe("auth_refresh");
|
||||
expect(classifyFailoverReason(message, { provider: "openai-codex" })).toBe("auth_permanent");
|
||||
}
|
||||
});
|
||||
|
||||
it("does not make uncertain OAuth refresh wrappers terminal", () => {
|
||||
const message =
|
||||
"OAuth token refresh failed for openai-codex: file lock timeout for /tmp/agent/auth-profiles.json. Please try again or re-authenticate.";
|
||||
expect(classifyProviderRuntimeFailureKind(message)).toBe("auth_refresh");
|
||||
expect(classifyFailoverReason(message, { provider: "openai-codex" })).toBe("auth");
|
||||
});
|
||||
|
||||
it("keeps Codex entitlement and usage-limit payloads out of terminal auth", () => {
|
||||
const entitlementMessages = [
|
||||
"You've hit your usage limit. Upgrade to Plus to continue using Codex (https://chatgpt.com/explore/plus), try again after 11:34 AM.",
|
||||
"You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits, try again later.",
|
||||
'429 {"type":"error","error":{"type":"rate_limit_error","message":"You\\u0027ve hit your usage limit. Upgrade to Plus to continue using Codex (https://chatgpt.com/explore/plus), try again after 11:34 AM."}}',
|
||||
];
|
||||
for (const message of entitlementMessages) {
|
||||
expect(classifyProviderRuntimeFailureKind(message)).not.toBe("auth_refresh");
|
||||
expect(classifyFailoverReason(message, { provider: "openai-codex" })).toBe("rate_limit");
|
||||
}
|
||||
});
|
||||
|
||||
it("classifies OAuth refresh timeouts and lock contention distinctly", () => {
|
||||
|
||||
@@ -854,6 +854,10 @@ function classifyFailoverClassificationFromMessage(
|
||||
if (isBillingErrorMessage(raw)) {
|
||||
return toReasonClassification("billing");
|
||||
}
|
||||
const oauthRefreshFailure = classifyOAuthRefreshFailure(raw);
|
||||
if (oauthRefreshFailure?.reason) {
|
||||
return toReasonClassification("auth_permanent");
|
||||
}
|
||||
if (isAuthPermanentErrorMessage(raw)) {
|
||||
return toReasonClassification("auth_permanent");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user