From 72ef0a07de5efc4e5cbbb2cd131e394a0d96b09c Mon Sep 17 00:00:00 2001 From: Ricky Schema Cascade Date: Mon, 18 May 2026 22:41:01 +0200 Subject: [PATCH 1/2] fix(cli): preserve dangerouslyBypassApprovalsAndSandbox on standalone personas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit assertStandaloneHarnessSettings rebuilt the resolved HarnessSettings object field-by-field (reasoning, timeoutSeconds, sandboxMode, approvalPolicy, workspaceWriteNetworkAccess, webSearch) but never copied dangerouslyBypassApprovalsAndSandbox. Standalone local personas (intent + no extends) silently lost the flag before codex launch, so codex started with its default approval policy and kept prompting for confirmations — including MCP tool-call approvals. The extends/overlay merge path was unaffected (object spread preserves it); only the standalone path dropped it. Also adds the boolean type check to assertPartialHarnessSettingsShape for parity with the other codex-only settings, and a regression test asserting the flag survives standalone resolution. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/cli/src/local-personas.test.ts | 27 +++++++++++++++++++++++++ packages/cli/src/local-personas.ts | 13 +++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/local-personas.test.ts b/packages/cli/src/local-personas.test.ts index fe45e49b..73846c23 100644 --- a/packages/cli/src/local-personas.test.ts +++ b/packages/cli/src/local-personas.test.ts @@ -450,6 +450,33 @@ test('inputs are preserved on standalone local personas', () => { }); }); +test('dangerouslyBypassApprovalsAndSandbox is preserved on standalone local personas', () => { + withLayers(({ cwd, homeDir }) => { + writeJson(join(homeDir, 'standalone-bypass.json'), { + id: 'standalone-bypass', + intent: 'review', + description: 'Standalone persona that opts out of codex approvals.', + harness: 'codex', + model: 'openai-codex/gpt-5.3-codex', + systemPrompt: 'Do the work.', + harnessSettings: { + reasoning: 'medium', + timeoutSeconds: 900, + dangerouslyBypassApprovalsAndSandbox: true + } + }); + const loaded = loadLocalPersonas({ cwd, homeDir }); + assert.deepEqual(loaded.warnings, []); + const settings = loaded.byId.get('standalone-bypass')?.harnessSettings; + assert.ok(settings); + // Regression: assertStandaloneHarnessSettings used to rebuild the + // settings object field-by-field and drop this flag, so codex launched + // without --dangerously-bypass-approvals-and-sandbox and kept prompting + // (including for MCP tool calls). + assert.equal(settings?.dangerouslyBypassApprovalsAndSandbox, true); + }); +}); + test('optional input flag is preserved on standalone local personas', () => { withLayers(({ cwd, homeDir }) => { writeJson(join(homeDir, 'standalone-scaffolder.json'), { diff --git a/packages/cli/src/local-personas.ts b/packages/cli/src/local-personas.ts index cbe21570..f3a28e9e 100644 --- a/packages/cli/src/local-personas.ts +++ b/packages/cli/src/local-personas.ts @@ -659,7 +659,8 @@ function assertPartialHarnessSettingsShape(value: Record, conte sandboxMode, approvalPolicy, workspaceWriteNetworkAccess, - webSearch + webSearch, + dangerouslyBypassApprovalsAndSandbox } = value; if ( reasoning !== undefined && @@ -695,6 +696,12 @@ function assertPartialHarnessSettingsShape(value: Record, conte if (webSearch !== undefined && typeof webSearch !== 'boolean') { throw new Error(`${context}.webSearch must be a boolean`); } + if ( + dangerouslyBypassApprovalsAndSandbox !== undefined && + typeof dangerouslyBypassApprovalsAndSandbox !== 'boolean' + ) { + throw new Error(`${context}.dangerouslyBypassApprovalsAndSandbox must be a boolean`); + } } function findInLibrary(key: string): PersonaSpec | undefined { @@ -746,6 +753,10 @@ function assertStandaloneHarnessSettings( if (settings.webSearch !== undefined) { out.webSearch = settings.webSearch as boolean; } + if (settings.dangerouslyBypassApprovalsAndSandbox !== undefined) { + out.dangerouslyBypassApprovalsAndSandbox = + settings.dangerouslyBypassApprovalsAndSandbox as boolean; + } return out; } From 2141cd5025182f7cf4329c9b5feaa6db9b7a34e7 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 20:52:06 +0000 Subject: [PATCH 2/2] fix(cli): centralize standalone harness settings parsing --- packages/cli/src/local-personas.test.ts | 26 ++++++++++++++++++++ packages/cli/src/local-personas.ts | 32 +++---------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/cli/src/local-personas.test.ts b/packages/cli/src/local-personas.test.ts index 73846c23..6bc1c750 100644 --- a/packages/cli/src/local-personas.test.ts +++ b/packages/cli/src/local-personas.test.ts @@ -477,6 +477,32 @@ test('dangerouslyBypassApprovalsAndSandbox is preserved on standalone local pers }); }); +test('standalone local personas reject bypass with explicit codex sandbox settings', () => { + withLayers(({ cwd, homeDir }) => { + writeJson(join(homeDir, 'standalone-bypass-conflict.json'), { + id: 'standalone-bypass-conflict', + intent: 'review', + description: 'Standalone persona with contradictory codex settings.', + harness: 'codex', + model: 'openai-codex/gpt-5.3-codex', + systemPrompt: 'Do the work.', + harnessSettings: { + reasoning: 'medium', + timeoutSeconds: 900, + sandboxMode: 'workspace-write', + dangerouslyBypassApprovalsAndSandbox: true + } + }); + + const loaded = loadLocalPersonas({ cwd, homeDir }); + assert.equal(loaded.byId.has('standalone-bypass-conflict'), false); + assert.match( + loaded.warnings.join('\n'), + /dangerouslyBypassApprovalsAndSandbox is mutually exclusive with: sandboxMode/ + ); + }); +}); + test('optional input flag is preserved on standalone local personas', () => { withLayers(({ cwd, homeDir }) => { writeJson(join(homeDir, 'standalone-scaffolder.json'), { diff --git a/packages/cli/src/local-personas.ts b/packages/cli/src/local-personas.ts index f3a28e9e..07f39bf7 100644 --- a/packages/cli/src/local-personas.ts +++ b/packages/cli/src/local-personas.ts @@ -17,7 +17,8 @@ import { type PersonaPermissions, type PersonaSpec, type PersonaTag, - type SidecarMdMode + type SidecarMdMode, + parseHarnessSettings } from '@agentworkforce/persona-kit'; import { listBuiltInPersonas, personaCatalog } from '@agentworkforce/workload-router'; @@ -730,34 +731,7 @@ function assertStandaloneHarnessSettings( settings: Record, context: string ): HarnessSettings { - assertPartialHarnessSettingsShape(settings, context); - const reasoning = settings.reasoning; - if (reasoning !== 'low' && reasoning !== 'medium' && reasoning !== 'high') { - throw new Error(`${context}.reasoning must be one of: low, medium, high`); - } - const timeoutSeconds = settings.timeoutSeconds; - if (typeof timeoutSeconds !== 'number' || !Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) { - throw new Error(`${context}.timeoutSeconds must be a positive number`); - } - - const out: HarnessSettings = { reasoning, timeoutSeconds }; - if (settings.sandboxMode !== undefined) { - out.sandboxMode = settings.sandboxMode as CodexSandboxMode; - } - if (settings.approvalPolicy !== undefined) { - out.approvalPolicy = settings.approvalPolicy as CodexApprovalPolicy; - } - if (settings.workspaceWriteNetworkAccess !== undefined) { - out.workspaceWriteNetworkAccess = settings.workspaceWriteNetworkAccess as boolean; - } - if (settings.webSearch !== undefined) { - out.webSearch = settings.webSearch as boolean; - } - if (settings.dangerouslyBypassApprovalsAndSandbox !== undefined) { - out.dangerouslyBypassApprovalsAndSandbox = - settings.dangerouslyBypassApprovalsAndSandbox as boolean; - } - return out; + return parseHarnessSettings(settings, context); } function standaloneSpecFromOverride(