Skip to content

Commit aca0761

Browse files
khaliqgantRicky Schema Cascadeclaudecoderabbitai[bot]
authored
fix(cli): preserve dangerouslyBypassApprovalsAndSandbox on standalone personas (#125)
* fix(cli): preserve dangerouslyBypassApprovalsAndSandbox on standalone personas 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) <noreply@anthropic.com> * fix(cli): centralize standalone harness settings parsing --------- Co-authored-by: Ricky Schema Cascade <ricky@agent-relay.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 1a9352e commit aca0761

2 files changed

Lines changed: 64 additions & 26 deletions

File tree

packages/cli/src/local-personas.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,59 @@ test('inputs are preserved on standalone local personas', () => {
450450
});
451451
});
452452

453+
test('dangerouslyBypassApprovalsAndSandbox is preserved on standalone local personas', () => {
454+
withLayers(({ cwd, homeDir }) => {
455+
writeJson(join(homeDir, 'standalone-bypass.json'), {
456+
id: 'standalone-bypass',
457+
intent: 'review',
458+
description: 'Standalone persona that opts out of codex approvals.',
459+
harness: 'codex',
460+
model: 'openai-codex/gpt-5.3-codex',
461+
systemPrompt: 'Do the work.',
462+
harnessSettings: {
463+
reasoning: 'medium',
464+
timeoutSeconds: 900,
465+
dangerouslyBypassApprovalsAndSandbox: true
466+
}
467+
});
468+
const loaded = loadLocalPersonas({ cwd, homeDir });
469+
assert.deepEqual(loaded.warnings, []);
470+
const settings = loaded.byId.get('standalone-bypass')?.harnessSettings;
471+
assert.ok(settings);
472+
// Regression: assertStandaloneHarnessSettings used to rebuild the
473+
// settings object field-by-field and drop this flag, so codex launched
474+
// without --dangerously-bypass-approvals-and-sandbox and kept prompting
475+
// (including for MCP tool calls).
476+
assert.equal(settings?.dangerouslyBypassApprovalsAndSandbox, true);
477+
});
478+
});
479+
480+
test('standalone local personas reject bypass with explicit codex sandbox settings', () => {
481+
withLayers(({ cwd, homeDir }) => {
482+
writeJson(join(homeDir, 'standalone-bypass-conflict.json'), {
483+
id: 'standalone-bypass-conflict',
484+
intent: 'review',
485+
description: 'Standalone persona with contradictory codex settings.',
486+
harness: 'codex',
487+
model: 'openai-codex/gpt-5.3-codex',
488+
systemPrompt: 'Do the work.',
489+
harnessSettings: {
490+
reasoning: 'medium',
491+
timeoutSeconds: 900,
492+
sandboxMode: 'workspace-write',
493+
dangerouslyBypassApprovalsAndSandbox: true
494+
}
495+
});
496+
497+
const loaded = loadLocalPersonas({ cwd, homeDir });
498+
assert.equal(loaded.byId.has('standalone-bypass-conflict'), false);
499+
assert.match(
500+
loaded.warnings.join('\n'),
501+
/dangerouslyBypassApprovalsAndSandbox is mutually exclusive with: sandboxMode/
502+
);
503+
});
504+
});
505+
453506
test('optional input flag is preserved on standalone local personas', () => {
454507
withLayers(({ cwd, homeDir }) => {
455508
writeJson(join(homeDir, 'standalone-scaffolder.json'), {

packages/cli/src/local-personas.ts

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {
1717
type PersonaPermissions,
1818
type PersonaSpec,
1919
type PersonaTag,
20-
type SidecarMdMode
20+
type SidecarMdMode,
21+
parseHarnessSettings
2122
} from '@agentworkforce/persona-kit';
2223
import { listBuiltInPersonas, personaCatalog } from '@agentworkforce/workload-router';
2324

@@ -659,7 +660,8 @@ function assertPartialHarnessSettingsShape(value: Record<string, unknown>, conte
659660
sandboxMode,
660661
approvalPolicy,
661662
workspaceWriteNetworkAccess,
662-
webSearch
663+
webSearch,
664+
dangerouslyBypassApprovalsAndSandbox
663665
} = value;
664666
if (
665667
reasoning !== undefined &&
@@ -695,6 +697,12 @@ function assertPartialHarnessSettingsShape(value: Record<string, unknown>, conte
695697
if (webSearch !== undefined && typeof webSearch !== 'boolean') {
696698
throw new Error(`${context}.webSearch must be a boolean`);
697699
}
700+
if (
701+
dangerouslyBypassApprovalsAndSandbox !== undefined &&
702+
typeof dangerouslyBypassApprovalsAndSandbox !== 'boolean'
703+
) {
704+
throw new Error(`${context}.dangerouslyBypassApprovalsAndSandbox must be a boolean`);
705+
}
698706
}
699707

700708
function findInLibrary(key: string): PersonaSpec | undefined {
@@ -723,30 +731,7 @@ function assertStandaloneHarnessSettings(
723731
settings: Record<string, unknown>,
724732
context: string
725733
): HarnessSettings {
726-
assertPartialHarnessSettingsShape(settings, context);
727-
const reasoning = settings.reasoning;
728-
if (reasoning !== 'low' && reasoning !== 'medium' && reasoning !== 'high') {
729-
throw new Error(`${context}.reasoning must be one of: low, medium, high`);
730-
}
731-
const timeoutSeconds = settings.timeoutSeconds;
732-
if (typeof timeoutSeconds !== 'number' || !Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
733-
throw new Error(`${context}.timeoutSeconds must be a positive number`);
734-
}
735-
736-
const out: HarnessSettings = { reasoning, timeoutSeconds };
737-
if (settings.sandboxMode !== undefined) {
738-
out.sandboxMode = settings.sandboxMode as CodexSandboxMode;
739-
}
740-
if (settings.approvalPolicy !== undefined) {
741-
out.approvalPolicy = settings.approvalPolicy as CodexApprovalPolicy;
742-
}
743-
if (settings.workspaceWriteNetworkAccess !== undefined) {
744-
out.workspaceWriteNetworkAccess = settings.workspaceWriteNetworkAccess as boolean;
745-
}
746-
if (settings.webSearch !== undefined) {
747-
out.webSearch = settings.webSearch as boolean;
748-
}
749-
return out;
734+
return parseHarnessSettings(settings, context);
750735
}
751736

752737
function standaloneSpecFromOverride(

0 commit comments

Comments
 (0)