mirror of
https://github.com/nexu-io/open-design.git
synced 2026-07-03 12:27:55 +08:00
Make WSL agent setup unambiguous for MCP installs (#4655)
Windows users running agent CLIs inside WSL were landing on the native Windows guide and copying the README MCP command directly, which left Linux /usr/bin/od shadowing, daemon origin, Node ABI, and Codex config parse failures unexplained. Add a WSL2 guide and teach the Codex config normalizer to drop nested feature tables that current Codex parses as maps instead of boolean flags. Constraint: WSL agent CLIs need the same Linux environment for the wrapper, daemon, Node modules, and credentials Rejected: Docs-only workaround | would leave daemon-launched Codex runs failing on nested features tables Confidence: high Scope-risk: narrow Directive: Keep WSL-specific guidance separate from native Windows PowerShell troubleshooting Tested: pnpm --filter @open-design/daemon exec vitest run -c vitest.config.ts tests/codex-config-normalize.test.ts Tested: pnpm --filter @open-design/daemon typecheck Tested: pnpm guard Tested: git diff --check Not-tested: Manual WSL2 end-to-end MCP install on a Windows host Related: Fixes #4648
This commit is contained in:
@@ -8,7 +8,7 @@ Run the full product locally.
|
|||||||
|
|
||||||
- **Node.js:** `~24` (Node 24.x). The repo enforces this through `package.json#engines`.
|
- **Node.js:** `~24` (Node 24.x). The repo enforces this through `package.json#engines`.
|
||||||
- **pnpm:** `10.33.x`. The repo pins `pnpm@10.33.2` through `packageManager`; use Corepack so the pinned version is selected automatically.
|
- **pnpm:** `10.33.x`. The repo pins `pnpm@10.33.2` through `packageManager`; use Corepack so the pinned version is selected automatically.
|
||||||
- **OS:** macOS, Linux, and WSL2 are the primary paths. Windows native is supported; see [`docs/windows-troubleshooting.md`](docs/windows-troubleshooting.md) for common setup gotchas.
|
- **OS:** macOS, Linux, and WSL2 are the primary paths. If your agent CLIs run inside WSL2, use the [`WSL2 setup guide`](docs/wsl-setup.md). Windows native is supported; see [`docs/windows-troubleshooting.md`](docs/windows-troubleshooting.md) for common PowerShell setup gotchas.
|
||||||
- **Optional local agent CLI:** Claude Code, Codex, Devin for Terminal, Gemini CLI, OpenCode, Cursor Agent, Qwen, Qoder CLI, GitHub Copilot CLI, etc. If none are installed, use the BYOK API mode from Settings.
|
- **Optional local agent CLI:** Claude Code, Codex, Devin for Terminal, Gemini CLI, OpenCode, Cursor Agent, Qwen, Qoder CLI, GitHub Copilot CLI, etc. If none are installed, use the BYOK API mode from Settings.
|
||||||
|
|
||||||
### Local agent CLI and PATH
|
### Local agent CLI and PATH
|
||||||
|
|||||||
@@ -307,6 +307,10 @@ od mcp install <agent>
|
|||||||
# | pi | vibe | hermes | cline | kimi | trae | opencode
|
# | pi | vibe | hermes | cline | kimi | trae | opencode
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **WSL2 users:** If your coding-agent CLIs run inside WSL2, follow the
|
||||||
|
> [`WSL2 setup guide`](docs/wsl-setup.md) first. Linux's `/usr/bin/od` can
|
||||||
|
> shadow Open Design's `od` command.
|
||||||
|
|
||||||
Then, inside the agent:
|
Then, inside the agent:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -343,7 +347,7 @@ corepack enable && pnpm install
|
|||||||
pnpm tools-dev run web
|
pnpm tools-dev run web
|
||||||
```
|
```
|
||||||
|
|
||||||
Node `~24`, pnpm `10.33.x`. Windows users, see [`docs/windows-troubleshooting.md`](docs/windows-troubleshooting.md). Full quickstart, env vars, Nix flake, and packaged build flow → [`QUICKSTART.md`](QUICKSTART.md).
|
Node `~24`, pnpm `10.33.x`. WSL2 users, see [`docs/wsl-setup.md`](docs/wsl-setup.md); native Windows users, see [`docs/windows-troubleshooting.md`](docs/windows-troubleshooting.md). Full quickstart, env vars, Nix flake, and packaged build flow → [`QUICKSTART.md`](QUICKSTART.md).
|
||||||
|
|
||||||
### A full workflow — from brief to artifact
|
### A full workflow — from brief to artifact
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,14 @@
|
|||||||
// intentionally scoped: only standalone `service_tier` key lines are touched;
|
// intentionally scoped: only standalone `service_tier` key lines are touched;
|
||||||
// everything else in config.toml is preserved verbatim.
|
// everything else in config.toml is preserved verbatim.
|
||||||
//
|
//
|
||||||
// The normalization is idempotent: if the file is absent or every service_tier
|
// The normalizer also removes nested `[features.*]` tables. Current Codex CLI
|
||||||
// value is already valid, it is left unchanged.
|
// configs model `[features]` as a map of boolean flags; a nested table makes a
|
||||||
|
// flag value a TOML map and the CLI exits with:
|
||||||
|
//
|
||||||
|
// invalid type: map, expected a boolean in `features`
|
||||||
|
//
|
||||||
|
// The normalization is idempotent: if the file is absent, or if no invalid
|
||||||
|
// service_tier value or nested features table is present, it is left unchanged.
|
||||||
|
|
||||||
import { randomBytes } from 'node:crypto';
|
import { randomBytes } from 'node:crypto';
|
||||||
import { rename, readFile, unlink, writeFile } from 'node:fs/promises';
|
import { rename, readFile, unlink, writeFile } from 'node:fs/promises';
|
||||||
@@ -61,6 +67,51 @@ export function resolveCodexConfigPath(
|
|||||||
*/
|
*/
|
||||||
const VALID_SERVICE_TIERS = new Set(['fast', 'flex']);
|
const VALID_SERVICE_TIERS = new Set(['fast', 'flex']);
|
||||||
|
|
||||||
|
function splitLinesPreservingEndings(content: string): string[] {
|
||||||
|
const lines = content.match(/[^\r\n]*(?:\r\n|\n|\r|$)/g) ?? [];
|
||||||
|
if (lines.at(-1) === '') lines.pop();
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tableHeaderName(line: string): string | null {
|
||||||
|
const withoutLineEnding = line.replace(/\r\n$|\n$|\r$/, '');
|
||||||
|
const trimmed = withoutLineEnding.trim();
|
||||||
|
const arrayHeader = trimmed.match(/^\[\[([^\]\r\n]+)\]\][^\S\r\n]*(?:#.*)?$/);
|
||||||
|
const arrayHeaderName = arrayHeader?.[1];
|
||||||
|
if (arrayHeaderName) return arrayHeaderName.trim();
|
||||||
|
const tableHeader = trimmed.match(/^\[([^\]\r\n]+)\][^\S\r\n]*(?:#.*)?$/);
|
||||||
|
const tableName = tableHeader?.[1];
|
||||||
|
if (tableName) return tableName.trim();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeNestedFeaturesTables(content: string): string | null {
|
||||||
|
const lines = splitLinesPreservingEndings(content);
|
||||||
|
let changed = false;
|
||||||
|
let droppingNestedFeaturesTable = false;
|
||||||
|
const kept: string[] = [];
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const headerName = tableHeaderName(line);
|
||||||
|
if (headerName) {
|
||||||
|
droppingNestedFeaturesTable = headerName.startsWith('features.');
|
||||||
|
if (droppingNestedFeaturesTable) {
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (droppingNestedFeaturesTable) {
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
kept.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed ? kept.join('') : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize the `service_tier` field in a config.toml string.
|
* Normalize the `service_tier` field in a config.toml string.
|
||||||
*
|
*
|
||||||
@@ -68,6 +119,9 @@ const VALID_SERVICE_TIERS = new Set(['fast', 'flex']);
|
|||||||
* {@link VALID_SERVICE_TIERS} has its entire line removed (so the Codex CLI
|
* {@link VALID_SERVICE_TIERS} has its entire line removed (so the Codex CLI
|
||||||
* uses its built-in default tier). Valid values are left verbatim.
|
* uses its built-in default tier). Valid values are left verbatim.
|
||||||
*
|
*
|
||||||
|
* Any nested `[features.*]` table is also removed because current Codex CLI
|
||||||
|
* configs expect `[features]` entries to be booleans, not maps.
|
||||||
|
*
|
||||||
* Returns `null` when nothing needed to change, otherwise the patched content.
|
* Returns `null` when nothing needed to change, otherwise the patched content.
|
||||||
*/
|
*/
|
||||||
export function normalizeCodexConfigContent(content: string): string | null {
|
export function normalizeCodexConfigContent(content: string): string | null {
|
||||||
@@ -94,7 +148,7 @@ export function normalizeCodexConfigContent(content: string): string | null {
|
|||||||
/^([^\S\r\n]*)service_tier([^\S\r\n]*=[^\S\r\n]*)(["'])([^"'\r\n]*)\3([^\S\r\n]*(?:#[^\r\n]*)?)(\r?\n|$)/gm;
|
/^([^\S\r\n]*)service_tier([^\S\r\n]*=[^\S\r\n]*)(["'])([^"'\r\n]*)\3([^\S\r\n]*(?:#[^\r\n]*)?)(\r?\n|$)/gm;
|
||||||
|
|
||||||
let changed = false;
|
let changed = false;
|
||||||
const patched = content.replace(
|
const serviceTierPatched = content.replace(
|
||||||
pattern,
|
pattern,
|
||||||
(match: string, _indent, _eq, _quote, value: string) => {
|
(match: string, _indent, _eq, _quote, value: string) => {
|
||||||
if (VALID_SERVICE_TIERS.has(value)) {
|
if (VALID_SERVICE_TIERS.has(value)) {
|
||||||
@@ -107,7 +161,12 @@ export function normalizeCodexConfigContent(content: string): string | null {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return changed ? patched : null;
|
const featuresPatched = removeNestedFeaturesTables(serviceTierPatched);
|
||||||
|
if (featuresPatched !== null) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed ? (featuresPatched ?? serviceTierPatched) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -195,6 +195,49 @@ describe('normalizeCodexConfigContent', () => {
|
|||||||
expect(normalizeCodexConfigContent(input)).toBeNull();
|
expect(normalizeCodexConfigContent(input)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('removes nested features tables that make Codex parse features as maps (#4648)', () => {
|
||||||
|
const input = [
|
||||||
|
'[features]',
|
||||||
|
'hide_spawn_agent_metadata = false',
|
||||||
|
'[features.multi_agent_v2]',
|
||||||
|
'hide_spawn_agent_metadata = false',
|
||||||
|
'max_concurrent_threads_per_session = 10000',
|
||||||
|
'enabled = false',
|
||||||
|
'',
|
||||||
|
'[model]',
|
||||||
|
'model = "gpt-5.5"',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = normalizeCodexConfigContent(input);
|
||||||
|
|
||||||
|
expect(result).toBe([
|
||||||
|
'[features]',
|
||||||
|
'hide_spawn_agent_metadata = false',
|
||||||
|
'[model]',
|
||||||
|
'model = "gpt-5.5"',
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves unrelated dotted tables while removing nested features tables', () => {
|
||||||
|
const input = [
|
||||||
|
'[profiles.default]',
|
||||||
|
'model = "gpt-5.5"',
|
||||||
|
'[features.multi_agent_v2]',
|
||||||
|
'enabled = false',
|
||||||
|
'[projects."/tmp/open-design"]',
|
||||||
|
'trust_level = "trusted"',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const result = normalizeCodexConfigContent(input);
|
||||||
|
|
||||||
|
expect(result).toBe([
|
||||||
|
'[profiles.default]',
|
||||||
|
'model = "gpt-5.5"',
|
||||||
|
'[projects."/tmp/open-design"]',
|
||||||
|
'trust_level = "trusted"',
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// CRLF regression: config.toml files written by the Codex app on Windows use
|
// CRLF regression: config.toml files written by the Codex app on Windows use
|
||||||
// CRLF endings. Removing the stale line must preserve ALL surrounding \r\n
|
// CRLF endings. Removing the stale line must preserve ALL surrounding \r\n
|
||||||
@@ -216,6 +259,17 @@ describe('normalizeCodexConfigContent', () => {
|
|||||||
// No naked LF introduced.
|
// No naked LF introduced.
|
||||||
expect(result).not.toMatch(/(?<!\r)\n/);
|
expect(result).not.toMatch(/(?<!\r)\n/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('CRLF regression: removes nested features tables while preserving surrounding \\r\\n endings', () => {
|
||||||
|
const crlfContent = '[features]\r\nhide_spawn_agent_metadata = false\r\n[features.multi_agent_v2]\r\nenabled = false\r\n[model]\r\nmodel = "gpt-5.5"\r\n';
|
||||||
|
const result = normalizeCodexConfigContent(crlfContent);
|
||||||
|
|
||||||
|
expect(result).not.toBeNull();
|
||||||
|
expect(result).not.toContain('[features.multi_agent_v2]');
|
||||||
|
expect(result).toBe('[features]\r\nhide_spawn_agent_metadata = false\r\n[model]\r\nmodel = "gpt-5.5"\r\n');
|
||||||
|
expect(result).not.toMatch(/\r(?!\n)/);
|
||||||
|
expect(result).not.toMatch(/(?<!\r)\n/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -284,6 +338,34 @@ describe('normalizeCodexConfigFile', () => {
|
|||||||
expect(after).toContain('model = "gpt-5.5"');
|
expect(after).toContain('model = "gpt-5.5"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('removes nested features tables from config.toml (#4648 regression)', async () => {
|
||||||
|
const configPath = join(tmpDir, 'config.toml');
|
||||||
|
writeFileSync(
|
||||||
|
configPath,
|
||||||
|
[
|
||||||
|
'[features]',
|
||||||
|
'hide_spawn_agent_metadata = false',
|
||||||
|
'[features.multi_agent_v2]',
|
||||||
|
'hide_spawn_agent_metadata = false',
|
||||||
|
'max_concurrent_threads_per_session = 10000',
|
||||||
|
'enabled = false',
|
||||||
|
'[model]',
|
||||||
|
'model = "gpt-5.5"',
|
||||||
|
].join('\n'),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
|
||||||
|
await normalizeCodexConfigFile({ CODEX_HOME: tmpDir });
|
||||||
|
|
||||||
|
const after = readFileSync(configPath, 'utf8');
|
||||||
|
expect(after).toContain('[features]');
|
||||||
|
expect(after).toContain('hide_spawn_agent_metadata = false');
|
||||||
|
expect(after).not.toContain('[features.multi_agent_v2]');
|
||||||
|
expect(after).not.toContain('max_concurrent_threads_per_session');
|
||||||
|
expect(after).toContain('[model]');
|
||||||
|
expect(after).toContain('model = "gpt-5.5"');
|
||||||
|
});
|
||||||
|
|
||||||
it('does not modify config.toml when service_tier is already valid', async () => {
|
it('does not modify config.toml when service_tier is already valid', async () => {
|
||||||
const configPath = join(tmpDir, 'config.toml');
|
const configPath = join(tmpDir, 'config.toml');
|
||||||
const original = `service_tier = "fast"\n`;
|
const original = `service_tier = "fast"\n`;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Open Design runs on Windows natively, but the path is less travelled than macOS, Linux, or WSL2. This guide covers the most common errors you will hit on a fresh Windows machine and the exact fix for each.
|
Open Design runs on Windows natively, but the path is less travelled than macOS, Linux, or WSL2. This guide covers the most common errors you will hit on a fresh Windows machine and the exact fix for each.
|
||||||
|
|
||||||
> **Tip:** If you already have WSL2 set up, that is the smoothest path on Windows. This guide is for native Windows (PowerShell).
|
> **Tip:** If your coding-agent CLIs run inside WSL2, use the dedicated [`WSL2 setup guide`](wsl-setup.md). This guide is for native Windows (PowerShell).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
166
docs/wsl-setup.md
Normal file
166
docs/wsl-setup.md
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# WSL2 Setup Guide
|
||||||
|
|
||||||
|
Use this guide when your coding-agent CLIs run inside WSL2. In that setup,
|
||||||
|
install and run Open Design from WSL as well so the agent CLI, `od` command,
|
||||||
|
daemon, Node modules, and credentials all come from the same Linux environment.
|
||||||
|
|
||||||
|
For native Windows PowerShell setup, use
|
||||||
|
[`docs/windows-troubleshooting.md`](windows-troubleshooting.md) instead.
|
||||||
|
|
||||||
|
## Recommended shape
|
||||||
|
|
||||||
|
- Clone Open Design inside WSL2.
|
||||||
|
- Install Node `~24` and the repo-pinned pnpm (`10.33.2`) inside WSL2.
|
||||||
|
- Put a WSL-native `od` wrapper before `/usr/bin` on `PATH`.
|
||||||
|
- Start the daemon from WSL with `od --no-open`.
|
||||||
|
- Install MCP entries from the same WSL shell.
|
||||||
|
|
||||||
|
Do not assume the Windows desktop app's daemon is the right daemon for WSL
|
||||||
|
agent clients. WSL2 networking and Windows credential stores can make that path
|
||||||
|
ambiguous. A WSL-started daemon keeps the MCP clients and Open Design in the
|
||||||
|
same environment.
|
||||||
|
|
||||||
|
## 1. Install from source in WSL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/nexu-io/open-design.git ~/tools/open-design
|
||||||
|
cd ~/tools/open-design
|
||||||
|
|
||||||
|
node --version # should print v24.x.x
|
||||||
|
corepack enable
|
||||||
|
corepack pnpm --version # should print 10.33.2
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use `mise`, trust and install the repo toolchain before `pnpm install`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mise trust
|
||||||
|
mise install
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Fix the `od` command collision
|
||||||
|
|
||||||
|
Linux already ships `/usr/bin/od` (octal dump). If that binary wins on `PATH`,
|
||||||
|
commands such as `od mcp install claude` fail with file-not-found messages for
|
||||||
|
`mcp`, `install`, and the agent name.
|
||||||
|
|
||||||
|
Check what your shell resolves:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
type -a od
|
||||||
|
```
|
||||||
|
|
||||||
|
If `/usr/bin/od` appears before Open Design, create a wrapper in `~/.local/bin`
|
||||||
|
and make sure that directory is first on `PATH`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.local/bin
|
||||||
|
|
||||||
|
cat > ~/.local/bin/od <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
repo="$HOME/tools/open-design"
|
||||||
|
cd "$repo" || exit 127
|
||||||
|
|
||||||
|
if command -v mise >/dev/null 2>&1; then
|
||||||
|
exec mise exec -- pnpm exec od "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec corepack pnpm exec od "$@"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x ~/.local/bin/od
|
||||||
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
hash -r
|
||||||
|
type -a od
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected first result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
od is /home/<user>/.local/bin/od
|
||||||
|
```
|
||||||
|
|
||||||
|
`od.exe` is not a reliable workaround from WSL. It may resolve to a Windows
|
||||||
|
coreutils binary instead of Open Design, especially on machines with Windows
|
||||||
|
coreutils installed.
|
||||||
|
|
||||||
|
## 3. Start the daemon from WSL
|
||||||
|
|
||||||
|
Run the daemon from the same WSL environment that your agent CLIs use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/tools/open-design
|
||||||
|
od --no-open
|
||||||
|
```
|
||||||
|
|
||||||
|
In another WSL terminal, verify it is reachable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSf http://127.0.0.1:7456 >/dev/null && echo "Open Design daemon is reachable"
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave the daemon terminal running while using MCP integrations.
|
||||||
|
|
||||||
|
## 4. Install MCP entries
|
||||||
|
|
||||||
|
From WSL, run the installer for each agent CLI you use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
od mcp install claude
|
||||||
|
od mcp install opencode
|
||||||
|
od mcp install codex
|
||||||
|
od mcp install antigravity
|
||||||
|
od mcp install copilot
|
||||||
|
```
|
||||||
|
|
||||||
|
The installer writes to the agent config locations for the current WSL user,
|
||||||
|
for example `~/.claude.json`, `~/.config/opencode/opencode.json`,
|
||||||
|
`~/.codex/config.toml`, `~/.gemini/antigravity/mcp_config.json`, and
|
||||||
|
`~/.copilot/mcp-config.json`.
|
||||||
|
|
||||||
|
## Native module mismatch after changing Node versions
|
||||||
|
|
||||||
|
If dependencies were installed under Node 22 and Open Design later runs under
|
||||||
|
Node 24, native modules such as `better-sqlite3` can fail with a
|
||||||
|
`NODE_MODULE_VERSION` mismatch.
|
||||||
|
|
||||||
|
Reinstall under the active Node 24 runtime:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/tools/open-design
|
||||||
|
rm -rf node_modules
|
||||||
|
pnpm store prune
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Then verify the native module loads:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @open-design/daemon exec node -e "require('better-sqlite3')"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Codex config parse failures
|
||||||
|
|
||||||
|
If Codex fails before MCP install or a direct `codex` run with:
|
||||||
|
|
||||||
|
```text
|
||||||
|
invalid type: map, expected a boolean
|
||||||
|
in `features`
|
||||||
|
```
|
||||||
|
|
||||||
|
check `~/.codex/config.toml` for nested feature tables such as:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[features.multi_agent_v2]
|
||||||
|
hide_spawn_agent_metadata = false
|
||||||
|
max_concurrent_threads_per_session = 10000
|
||||||
|
enabled = false
|
||||||
|
```
|
||||||
|
|
||||||
|
Current Codex CLI versions expect `[features]` values to be booleans. Remove or
|
||||||
|
comment out the nested `[features.*]` block, then retry the command.
|
||||||
|
|
||||||
|
Open Design also normalizes this shape before daemon-launched Codex runs, but
|
||||||
|
manual cleanup may still be needed when Codex itself is invoked directly before
|
||||||
|
Open Design gets a chance to patch the config.
|
||||||
Reference in New Issue
Block a user