Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/persona-kit/src/interactive-spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,27 @@ test('codex translates sandbox harness settings to launch flags', () => {
]);
});

test('codex emits the single bypass flag when dangerouslyBypassApprovalsAndSandbox is set', () => {
const result = buildInteractiveSpec({
harness: 'codex',
personaId: 'test-persona',
model: 'openai-codex/gpt-5.3-codex',
systemPrompt: 'x',
harnessSettings: {
reasoning: 'high',
timeoutSeconds: 1200,
dangerouslyBypassApprovalsAndSandbox: true,
webSearch: true
}
});
assert.deepEqual(result.args, [
'-m',
'gpt-5.3-codex',
'--dangerously-bypass-approvals-and-sandbox',
'--search'
]);
});

test('codex translates http mcpServers into --config mcp_servers.* args', () => {
const result = buildInteractiveSpec({
harness: 'codex',
Expand Down
43 changes: 27 additions & 16 deletions packages/persona-kit/src/interactive-spec.ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning messages for claude/opencode omit newly added dangerouslyBypassApprovalsAndSandbox setting

The hasCodexLaunchSettings() function at packages/persona-kit/src/interactive-spec.ts:83-92 now detects dangerouslyBypassApprovalsAndSandbox, but the warning messages emitted by the claude (line 233) and opencode (line 303) branches still enumerate only the original four settings: sandboxMode, approvalPolicy, workspaceWriteNetworkAccess, and webSearch. If a persona sets only dangerouslyBypassApprovalsAndSandbox: true with a non-codex harness, the trigger fires but the warning names settings the user never set, making it confusing rather than helpful.

(Refers to line 233)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning message for opencode omit newly added dangerouslyBypassApprovalsAndSandbox setting

Same issue as the claude branch: the opencode warning at line 303 lists the four original codex-only settings but not the newly added dangerouslyBypassApprovalsAndSandbox. This triggers when hasCodexLaunchSettings() returns true due to the new field, producing a misleading warning.

(Refers to line 303)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,14 @@ function hasCodexLaunchSettings(settings: HarnessSettings | undefined): boolean
settings.sandboxMode ||
settings.approvalPolicy ||
settings.workspaceWriteNetworkAccess !== undefined ||
settings.webSearch
settings.webSearch ||
settings.dangerouslyBypassApprovalsAndSandbox !== undefined
);
}

const CODEX_ONLY_WARNING =
'persona declares codex-only harnessSettings but the {harness} harness ignores sandboxMode, approvalPolicy, workspaceWriteNetworkAccess, webSearch, and dangerouslyBypassApprovalsAndSandbox.';


function toTomlBasicString(value: string): string {
// JSON string escaping is compatible with TOML basic strings.
Expand Down Expand Up @@ -229,7 +233,7 @@ export function buildInteractiveSpec(input: BuildInteractiveSpecInput): Interact
}
if (hasCodexLaunchSettings(harnessSettings)) {
warnings.push(
'persona declares codex-only harnessSettings but the claude harness ignores sandboxMode, approvalPolicy, workspaceWriteNetworkAccess, and webSearch.'
CODEX_ONLY_WARNING.replace('{harness}', 'claude')
);
}
return { bin: 'claude', args, initialPrompt: null, warnings, configFiles: [] };
Expand All @@ -249,19 +253,26 @@ export function buildInteractiveSpec(input: BuildInteractiveSpecInput): Interact
if (mcpServers && Object.keys(mcpServers).length > 0) {
appendCodexMcpServerArgs(args, mcpServers, warnings);
}
if (harnessSettings?.sandboxMode) {
args.push('--sandbox', harnessSettings.sandboxMode);
}
if (harnessSettings?.approvalPolicy) {
args.push('--ask-for-approval', harnessSettings.approvalPolicy);
}
if (harnessSettings?.workspaceWriteNetworkAccess !== undefined) {
args.push(
'-c',
`sandbox_workspace_write.network_access=${String(
harnessSettings.workspaceWriteNetworkAccess
)}`
);
if (harnessSettings?.dangerouslyBypassApprovalsAndSandbox) {
// Single combined flag — collapses "no sandbox + never ask" and
// suppresses codex's interactive "are you sure?" startup
// confirmation. The two-flag form below still prompts.
args.push('--dangerously-bypass-approvals-and-sandbox');
} else {
if (harnessSettings?.sandboxMode) {
args.push('--sandbox', harnessSettings.sandboxMode);
}
if (harnessSettings?.approvalPolicy) {
args.push('--ask-for-approval', harnessSettings.approvalPolicy);
}
if (harnessSettings?.workspaceWriteNetworkAccess !== undefined) {
args.push(
'-c',
`sandbox_workspace_write.network_access=${String(
harnessSettings.workspaceWriteNetworkAccess
)}`
);
}
}
if (harnessSettings?.webSearch) {
args.push('--search');
Expand Down Expand Up @@ -292,7 +303,7 @@ export function buildInteractiveSpec(input: BuildInteractiveSpecInput): Interact
}
if (hasCodexLaunchSettings(harnessSettings)) {
warnings.push(
'persona declares codex-only harnessSettings but the opencode harness ignores sandboxMode, approvalPolicy, workspaceWriteNetworkAccess, and webSearch.'
CODEX_ONLY_WARNING.replace('{harness}', 'opencode')
);
}
// opencode resolves a persona's system prompt + model through its own
Expand Down
59 changes: 59 additions & 0 deletions packages/persona-kit/src/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,65 @@ test('parseHarnessSettings accepts optional codex fields and rejects bad ones',
);
});

test('parseHarnessSettings accepts dangerouslyBypassApprovalsAndSandbox alone', () => {
for (const value of [true, false]) {
const ok = parseHarnessSettings(
{
reasoning: 'high',
timeoutSeconds: 60,
dangerouslyBypassApprovalsAndSandbox: value
},
'rt'
);
assert.equal(ok.dangerouslyBypassApprovalsAndSandbox, value);
}
});

test('parseHarnessSettings rejects dangerouslyBypassApprovalsAndSandbox with conflicting fields', () => {
for (const conflict of ['sandboxMode', 'approvalPolicy', 'workspaceWriteNetworkAccess']) {
const overlay: Record<string, unknown> = {
reasoning: 'high',
timeoutSeconds: 60,
dangerouslyBypassApprovalsAndSandbox: true
};
if (conflict === 'sandboxMode') overlay.sandboxMode = 'workspace-write';
if (conflict === 'approvalPolicy') overlay.approvalPolicy = 'never';
if (conflict === 'workspaceWriteNetworkAccess') overlay.workspaceWriteNetworkAccess = true;
assert.throws(
() => parseHarnessSettings(overlay, 'rt'),
new RegExp(`mutually exclusive with: .*${conflict}`)
);
}
});

test('parseHarnessSettings rejects dangerouslyBypassApprovalsAndSandbox:false with conflicting fields', () => {
for (const conflict of ['sandboxMode', 'approvalPolicy', 'workspaceWriteNetworkAccess']) {
const overlay: Record<string, unknown> = {
reasoning: 'high',
timeoutSeconds: 60,
dangerouslyBypassApprovalsAndSandbox: false
};
if (conflict === 'sandboxMode') overlay.sandboxMode = 'workspace-write';
if (conflict === 'approvalPolicy') overlay.approvalPolicy = 'never';
if (conflict === 'workspaceWriteNetworkAccess') overlay.workspaceWriteNetworkAccess = true;
assert.throws(
() => parseHarnessSettings(overlay, 'rt'),
new RegExp(`mutually exclusive with: .*${conflict}`)
);
}
});

test('parseHarnessSettings rejects non-boolean dangerouslyBypassApprovalsAndSandbox', () => {
assert.throws(
() =>
parseHarnessSettings(
{ reasoning: 'high', timeoutSeconds: 60, dangerouslyBypassApprovalsAndSandbox: 'yes' },
'rt'
),
/dangerouslyBypassApprovalsAndSandbox must be a boolean/
);
});

test('parseTags rejects empty arrays and unknown tags', () => {
assert.throws(() => parseTags([], 'tags'), /must be a non-empty array/);
assert.throws(() => parseTags(['nonsense-tag'], 'tags'), /tags\[0\] must be one of:/);
Expand Down
22 changes: 21 additions & 1 deletion packages/persona-kit/src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ export function parseHarnessSettings(value: unknown, context: string): HarnessSe
sandboxMode,
approvalPolicy,
workspaceWriteNetworkAccess,
webSearch
webSearch,
dangerouslyBypassApprovalsAndSandbox
} = value;
if (!['low', 'medium', 'high'].includes(String(reasoning))) {
throw new Error(`${context}.reasoning must be low|medium|high`);
Expand Down Expand Up @@ -132,6 +133,25 @@ export function parseHarnessSettings(value: unknown, context: string): HarnessSe
}
out.webSearch = webSearch;
}
if (dangerouslyBypassApprovalsAndSandbox !== undefined) {
if (typeof dangerouslyBypassApprovalsAndSandbox !== 'boolean') {
throw new Error(`${context}.dangerouslyBypassApprovalsAndSandbox must be a boolean`);
}
// Reject mixed-shape configs whenever the field is *present*, not only
// when true. Co-declaring sandboxMode/approvalPolicy/workspaceWriteNetworkAccess
// with an explicit `false` is still a contradictory shape — the two-flag
// form and the single-flag form are mutually exclusive concepts.
const conflicts: string[] = [];
if (sandboxMode !== undefined) conflicts.push('sandboxMode');
if (approvalPolicy !== undefined) conflicts.push('approvalPolicy');
if (workspaceWriteNetworkAccess !== undefined) conflicts.push('workspaceWriteNetworkAccess');
if (conflicts.length > 0) {
throw new Error(
`${context}.dangerouslyBypassApprovalsAndSandbox is mutually exclusive with: ${conflicts.join(', ')}`
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
out.dangerouslyBypassApprovalsAndSandbox = dangerouslyBypassApprovalsAndSandbox;
}

return out;
}
Expand Down
9 changes: 9 additions & 0 deletions packages/persona-kit/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ export interface HarnessSettings {
workspaceWriteNetworkAccess?: boolean;
/** Enable the Codex live web-search tool for this runtime. */
webSearch?: boolean;
/**
* Emit codex's single `--dangerously-bypass-approvals-and-sandbox` flag,
* which collapses "no sandbox + never ask for approval" and also
* suppresses codex's interactive "are you sure?" startup confirmation.
* Mutually exclusive with `sandboxMode`, `approvalPolicy`, and
* `workspaceWriteNetworkAccess` — those translate to the two-flag form
* which still prompts.
*/
dangerouslyBypassApprovalsAndSandbox?: boolean;
}

export interface PersonaRuntime {
Expand Down
Loading