Skip to content
Draft
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
4 changes: 2 additions & 2 deletions docs/client-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Claude Code writes `.mcp.json`; Codex writes `.codex/config.toml`. Both configs

`--yes` writes the config without prompts. Run `init` without flags to choose a client interactively and optionally add project instructions. Cursor and OpenCode still use HTTP and need a separately running server.

Existing settings are kept. If a different Codebase Context entry is already present, setup leaves the file unchanged and reports the conflict. Restart the client after setup and approve the server if asked. The first use builds the code index. Rerun setup if the Codebase Context installation moves, because the config uses its installed location.
Existing settings are kept. If a different Codebase Context entry is already present, setup leaves the file unchanged and reports the conflict. If the Codebase Context installation moves, remove only its existing entry from the client config. Keep the other settings, then run setup again. Restart the client after setup and approve the server if asked. The first use builds the code index.

## Optional fixed-folder setup and CLI preparation

Expand All @@ -129,7 +129,7 @@ To prepare the index separately, run from the project folder:
npx -y codebase-context@2.4.0 reindex
```

The interactive wizard limitations described above are still present in the 2.4.0 source. A successful wizard exit alone does not establish working client registration.
A successful setup run writes the config but does not confirm that the client connected.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the declined-wizard caveat

In interactive mode, answering no to Apply MCP config? still lets handleInitCli print the next steps and return successfully without calling writeFile, so a successful setup run does not necessarily write a config. This sentence should distinguish a successful config application from a successful wizard exit rather than assuring users that the file was written.

AGENTS.md reference: AGENTS.md:L112-L112

Useful? React with 👍 / 👎.


## Gemini CLI

Expand Down
2 changes: 1 addition & 1 deletion src/cli-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ export async function _buildMergedCodexContent(
}

const dottedEntry = new RegExp(
`^\\s*${mcpServersKey}\\s*\\.\\s*${codebaseContextKey}\\s*\\.`,
`^\\s*${mcpServersKey}\\s*\\.\\s*${codebaseContextKey}\\s*(?:\\.|=)`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid matching dotted keys inside multiline TOML strings

When an existing Codex config has a multiline string such as developer_instructions containing an example line beginning mcp_servers.codebase-context =, this unanchored textual scan matches content inside the string and aborts initialization even though no such server entry exists. Restrict detection to actual root-level TOML key definitions or parse the TOML structure so unrelated saved settings do not prevent setup.

Useful? React with 👍 / 👎.

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.

P1 Saved examples block setup

The new = match also catches text inside TOML multiline strings. If a user includes mcp_servers.codebase-context = { command = "example" } on its own line in saved instructions, setup reports a conflicting server and exits without adding one, even though that line is only string content. Check actual TOML keys rather than matching every line, and add a saved-file test for this case.

'm'
);
if (dottedEntry.test(normalizedExisting)) {
Expand Down
59 changes: 59 additions & 0 deletions tests/cli-init-saved-file.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { describe, expect, it, vi } from 'vitest';
import { handleInitCli } from '../src/cli-init.js';

describe('Codex init with an existing saved config', () => {
it.each([
{
name: 'bare dotted keys',
toml: 'mcp_servers.codebase-context = { command = "old", args = [] }\n'
},
{
name: 'a double-quoted root key',
toml: '"mcp_servers".codebase-context = { command = "old", args = [] }\n'
},
{
name: 'a double-quoted server key',
toml: 'mcp_servers."codebase-context" = { command = "old", args = [] }\n'
},
{
name: 'double-quoted root and server keys',
toml: '"mcp_servers"."codebase-context" = { command = "old", args = [] }\n'
},
{
name: 'single-quoted root and server keys',
toml: "'mcp_servers'.'codebase-context' = { command = \"old\", args = [] }\n"
}
])('refuses $name without changing the saved file', async ({ toml }) => {
const projectRoot = await mkdtemp(path.join(os.tmpdir(), 'cbc-codex-init-'));
const configPath = path.join(projectRoot, '.codex', 'config.toml');
const initialBytes = Buffer.from(toml, 'utf8');
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const previousExitCode = process.exitCode;

try {
await mkdir(path.dirname(configPath), { recursive: true });
await writeFile(configPath, initialBytes);
process.exitCode = undefined;

await handleInitCli(['--client', 'codex', '--yes', '--root', projectRoot]);

const persistedBytes = await readFile(configPath);
expect({ exitCode: process.exitCode, persistedBytes }).toEqual({
exitCode: 1,
persistedBytes: initialBytes
});
expect(errorSpy).toHaveBeenCalledWith(
expect.stringContaining('already contains a different codebase-context configuration')
);
} finally {
process.exitCode = previousExitCode;
errorSpy.mockRestore();
logSpy.mockRestore();
await rm(projectRoot, { recursive: true, force: true });
}
});
});
Loading