docs: disclose codex isolation limits

This commit is contained in:
Andrey Avtomonov 2026-06-01 18:10:09 +02:00
parent 5966a09c49
commit 27bedb2879
8 changed files with 68 additions and 2 deletions

View file

@ -0,0 +1,9 @@
export const CODEX_ISOLATION_WARNING =
'Codex backend isolation is limited by the public Codex SDK/CLI surface: ktx restricts the runtime MCP server to the current ktx tool set, disables Codex web search, asks for a read-only sandbox, and sets approval_policy=never, but Codex may still load user Codex config and built-in command execution or read-only file capabilities.';
export const CODEX_ISOLATION_WARNING_FIX =
'Use llm.provider.backend: claude-code when you need stricter Claude-Code-style runtime tool isolation, or remove host Codex MCP/tool config before running untrusted prompts through the codex backend.';
export function formatCodexIsolationWarning(): string {
return `${CODEX_ISOLATION_WARNING} ${CODEX_ISOLATION_WARNING_FIX}`;
}

View file

@ -2,6 +2,7 @@ import { execFile } from 'node:child_process';
import { writeFile } from 'node:fs/promises';
import { promisify } from 'node:util';
import { runClaudeCodeAuthProbe } from './context/llm/claude-code-runtime.js';
import { formatCodexIsolationWarning } from './context/llm/codex-isolation.js';
import { runCodexAuthProbe } from './context/llm/codex-runtime.js';
import { resolveLocalKtxLlmConfig } from './context/llm/local-config.js';
import { resolveKtxConfigReference } from './context/core/config-reference.js';
@ -1113,6 +1114,7 @@ export async function runKtxSetupAnthropicModelStep(
io.stderr.write(`${health.message}\n`);
return { status: 'failed', projectDir: args.projectDir };
}
io.stderr.write(`${formatCodexIsolationWarning()}\n`);
await persistLlmConfig(args.projectDir, { backend: 'codex' }, model.model);
io.stdout.write(`│ LLM ready: yes (codex, ${model.model})\n`);
return { status: 'ready', projectDir: args.projectDir };

View file

@ -1,6 +1,10 @@
import { stat as statAsync, readdir as readdirAsync } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { runClaudeCodeAuthProbe } from './context/llm/claude-code-runtime.js';
import {
CODEX_ISOLATION_WARNING,
CODEX_ISOLATION_WARNING_FIX,
} from './context/llm/codex-isolation.js';
import { runCodexAuthProbe } from './context/llm/codex-runtime.js';
import type { KtxConfigIssue, KtxProjectConfig, KtxProjectConnectionConfig, KtxProjectEmbeddingConfig, KtxProjectLlmConfig } from './context/project/config.js';
import type { KtxLocalProject } from './context/project/project.js';
@ -609,6 +613,13 @@ function buildWarnings(
});
}
if (llm.backend === 'codex') {
warnings.push({
message: CODEX_ISOLATION_WARNING,
fix: CODEX_ISOLATION_WARNING_FIX,
});
}
return warnings;
}

View file

@ -0,0 +1,19 @@
import { describe, expect, it } from 'vitest';
import {
CODEX_ISOLATION_WARNING,
CODEX_ISOLATION_WARNING_FIX,
formatCodexIsolationWarning,
} from '../../../src/context/llm/codex-isolation.js';
describe('Codex isolation warning', () => {
it('documents the enforced and unenforced Codex isolation boundaries', () => {
expect(CODEX_ISOLATION_WARNING).toContain('runtime MCP server to the current ktx tool set');
expect(CODEX_ISOLATION_WARNING).toContain('disables Codex web search');
expect(CODEX_ISOLATION_WARNING).toContain('may still load user Codex config');
expect(CODEX_ISOLATION_WARNING).toContain('built-in command execution');
expect(CODEX_ISOLATION_WARNING_FIX).toContain('claude-code');
expect(formatCodexIsolationWarning()).toBe(
`${CODEX_ISOLATION_WARNING} ${CODEX_ISOLATION_WARNING_FIX}`,
);
});
});

View file

@ -240,6 +240,8 @@ describe('setup Anthropic model step', () => {
models: { default: 'gpt-5.3-codex' },
});
expect(codexAuthProbe).toHaveBeenCalledWith(expect.objectContaining({ projectDir: tempDir, model: 'gpt-5.3-codex' }));
expect(io.stderr()).toContain('Codex backend isolation is limited');
expect(io.stderr()).toContain('may still load user Codex config');
});
it('prompts for the Claude Code model during interactive setup', async () => {

View file

@ -415,6 +415,16 @@ describe('buildProjectStatus codex', () => {
status: 'ok',
detail: 'local Codex session authenticated',
});
expect(status.warnings).toEqual(
expect.arrayContaining([
expect.objectContaining({
message: expect.stringContaining('Codex backend isolation is limited'),
fix: expect.stringContaining('claude-code'),
}),
]),
);
const rendered = renderProjectStatus(status, { verbose: false, useColor: false });
expect(rendered).toContain('Codex backend isolation is limited');
});
it('skips Codex auth probe with --fast', async () => {