diff --git a/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts b/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts index a09c2702d90..e08adcebffb 100644 --- a/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts +++ b/src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts @@ -242,6 +242,15 @@ describe("formatAssistantErrorText", () => { ); }); + it("does not misdiagnose generic Codex permission failures as missing-scope failures", () => { + const msg = makeAssistantError( + '403 {"type":"error","error":{"type":"permission_error","message":"Insufficient permissions for this organization"}}', + ); + expect(formatAssistantErrorText(msg, { provider: "openai-codex" })).not.toContain( + "required OpenAI Codex scopes", + ); + }); + it("returns an HTML-403 auth message for HTML provider auth failures", () => { const msg = makeAssistantError("403 Access denied"); expect(formatAssistantErrorText(msg)).toBe( diff --git a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts index 1bdbab37dc6..73e76320049 100644 --- a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts +++ b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts @@ -1124,6 +1124,16 @@ describe("classifyProviderRuntimeFailureKind", () => { ).not.toBe("auth_scope"); }); + it("does not treat generic Codex permission failures as missing scope failures", () => { + expect( + classifyProviderRuntimeFailureKind({ + provider: "openai-codex", + message: + '403 {"type":"error","error":{"type":"permission_error","message":"Insufficient permissions for this organization"}}', + }), + ).not.toBe("auth_scope"); + }); + it("classifies OAuth refresh failures", () => { expect( classifyProviderRuntimeFailureKind( diff --git a/src/agents/pi-embedded-helpers/errors.ts b/src/agents/pi-embedded-helpers/errors.ts index 9b91a903aa4..3041d01ed48 100644 --- a/src/agents/pi-embedded-helpers/errors.ts +++ b/src/agents/pi-embedded-helpers/errors.ts @@ -465,7 +465,7 @@ const TIMEOUT_ERROR_CODES = new Set([ "EAI_AGAIN", ]); const AUTH_SCOPE_HINT_RE = - /\b(?:missing|required|requires|insufficient)\s+(?:the\s+following\s+)?scopes?\b|\bmissing\s+scope\b|\binsufficient\s+permissions?\b/i; + /\b(?:missing|required|requires|insufficient)\s+(?:the\s+following\s+)?scopes?\b|\bmissing\s+scope\b/i; const AUTH_SCOPE_NAME_RE = /\b(?:api\.responses\.write|model\.request)\b/i; const HTML_BODY_RE = /^\s*(?:/i; diff --git a/src/agents/pi-embedded-runner.buildembeddedsandboxinfo.test.ts b/src/agents/pi-embedded-runner.buildembeddedsandboxinfo.test.ts index c3b8fb286ec..4a43112e810 100644 --- a/src/agents/pi-embedded-runner.buildembeddedsandboxinfo.test.ts +++ b/src/agents/pi-embedded-runner.buildembeddedsandboxinfo.test.ts @@ -119,7 +119,6 @@ describe("resolveEmbeddedFullAccessState", () => { it("treats direct host runs with allowed elevation as full-access available", () => { expect( resolveEmbeddedFullAccessState({ - sandboxEnabled: false, execElevated: { enabled: true, allowed: true, @@ -132,7 +131,6 @@ describe("resolveEmbeddedFullAccessState", () => { it("keeps explicit runtime blocks even when host exec is allowed", () => { expect( resolveEmbeddedFullAccessState({ - sandboxEnabled: false, execElevated: { enabled: true, allowed: true, diff --git a/src/agents/pi-embedded-runner/sandbox-info.ts b/src/agents/pi-embedded-runner/sandbox-info.ts index 6688cf38845..c6d9d3db72e 100644 --- a/src/agents/pi-embedded-runner/sandbox-info.ts +++ b/src/agents/pi-embedded-runner/sandbox-info.ts @@ -2,10 +2,10 @@ import type { ExecElevatedDefaults } from "../bash-tools.js"; import type { resolveSandboxContext } from "../sandbox.js"; import type { EmbeddedFullAccessBlockedReason, EmbeddedSandboxInfo } from "./types.js"; -export function resolveEmbeddedFullAccessState(params: { - sandboxEnabled: boolean; - execElevated?: ExecElevatedDefaults; -}): { available: boolean; blockedReason?: EmbeddedFullAccessBlockedReason } { +export function resolveEmbeddedFullAccessState(params: { execElevated?: ExecElevatedDefaults }): { + available: boolean; + blockedReason?: EmbeddedFullAccessBlockedReason; +} { if (params.execElevated?.fullAccessAvailable === true) { return { available: true }; } @@ -21,9 +21,6 @@ export function resolveEmbeddedFullAccessState(params: { blockedReason: "host-policy", }; } - if (!params.sandboxEnabled) { - return { available: true }; - } return { available: true }; } @@ -37,7 +34,6 @@ export function buildEmbeddedSandboxInfo( const elevatedConfigured = execElevated?.enabled === true; const elevatedAllowed = Boolean(execElevated?.enabled && execElevated.allowed); const fullAccess = resolveEmbeddedFullAccessState({ - sandboxEnabled: true, execElevated, }); return { diff --git a/src/auto-reply/reply/commands-system-prompt.ts b/src/auto-reply/reply/commands-system-prompt.ts index a9bd5f9e4d6..1ded528709a 100644 --- a/src/auto-reply/reply/commands-system-prompt.ts +++ b/src/auto-reply/reply/commands-system-prompt.ts @@ -112,7 +112,6 @@ export async function resolveCommandsSystemPromptBundle( }, }); const fullAccessState = resolveEmbeddedFullAccessState({ - sandboxEnabled: sandboxRuntime.sandboxed, execElevated: { enabled: params.elevated.enabled, allowed: params.elevated.allowed, diff --git a/src/auto-reply/reply/get-reply-run.exec-hint.test.ts b/src/auto-reply/reply/get-reply-run.exec-hint.test.ts index 356c52f6350..80e922f1386 100644 --- a/src/auto-reply/reply/get-reply-run.exec-hint.test.ts +++ b/src/auto-reply/reply/get-reply-run.exec-hint.test.ts @@ -48,7 +48,7 @@ describe("buildExecOverridePromptHint", () => { expect(result).toContain("Current elevated level: full."); expect(result).toContain( - "Auto-approved /elevated full is unavailable here (runtime). Use ask/on instead and do not ask the user to switch to /elevated full.", + "Auto-approved /elevated full is unavailable here (runtime). Do not ask the user to switch to /elevated full.", ); }); }); diff --git a/src/auto-reply/reply/get-reply-run.ts b/src/auto-reply/reply/get-reply-run.ts index 1fe49f7c240..cce1a875b81 100644 --- a/src/auto-reply/reply/get-reply-run.ts +++ b/src/auto-reply/reply/get-reply-run.ts @@ -4,7 +4,6 @@ import type { ExecToolDefaults } from "../../agents/bash-tools.js"; import { resolveFastModeState } from "../../agents/fast-mode.js"; import { resolveEmbeddedFullAccessState } from "../../agents/pi-embedded-runner/sandbox-info.js"; import type { EmbeddedFullAccessBlockedReason } from "../../agents/pi-embedded-runner/types.js"; -import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js"; import type { OpenClawConfig } from "../../config/config.js"; import { resolveGroupSessionKey } from "../../config/sessions/group.js"; import { @@ -76,7 +75,7 @@ export function buildExecOverridePromptHint(params: { const elevatedLine = `Current elevated level: ${params.elevatedLevel}.`; const fullAccessLine = params.fullAccessAvailable === false - ? `Auto-approved /elevated full is unavailable here (${params.fullAccessBlockedReason ?? "runtime"}). Use ask/on instead and do not ask the user to switch to /elevated full.` + ? `Auto-approved /elevated full is unavailable here (${params.fullAccessBlockedReason ?? "runtime"}). Do not ask the user to switch to /elevated full.` : undefined; return [ "## Current Exec Session State", @@ -226,10 +225,6 @@ export async function runPreparedReply( isFastTestEnv: process.env.OPENCLAW_TEST_FAST === "1", }); const fullAccessState = resolveEmbeddedFullAccessState({ - sandboxEnabled: resolveSandboxRuntimeStatus({ - cfg, - sessionKey, - }).sandboxed, execElevated: { enabled: elevatedEnabled, allowed: elevatedAllowed,