Skip to content

Commit 9ae5c0a

Browse files
xuiocodex
andcommitted
Remove legacy Claude Codex subagents surfaces
Co-Authored-By: OpenAI Codex <noreply@openai.com>
1 parent 3d8c098 commit 9ae5c0a

5 files changed

Lines changed: 106 additions & 9 deletions

File tree

dist/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26436,8 +26436,8 @@ var usageGuide = [
2643626436
"- Use codex_task_group when the work can be split into independent concurrent Codex tasks and Claude wants one combined response with rolled-up per-task findings.",
2643726437
"- Use codex_followup when Claude already has a session_id from codex_task or codex_task_group and wants to continue, steer, wait on, or cancel that same Codex context. This is a Codex-specific multi-turn extension; wait/cancel correspond to native TaskOutput/TaskStop-style operations.",
2643826438
"- Set codex_task background true for long-running work so Claude gets a session_id immediately, then use codex_wait_any for several sessions or codex_followup mode wait, steer, or cancel for one session.",
26439-
"- Subscribe to or read codex://sessions/{session_id} for background progress milestones and completion state. This is the Codex TaskGet/TaskList-style resource surface. The server emits notifications/resources/updated when meaningful Codex output changes.",
26440-
"- Diagnostics are resources by default: read codex://status, codex://doctor, or codex://usage when a prior call failed or availability is uncertain.",
26439+
"- Prefer codex_followup mode wait and codex_wait_any for completion. Subscribe to or read codex://sessions/{session_id} only when resource access is available and Claude needs progress milestones or completion state.",
26440+
"- Diagnostics are resources by default: read codex://status, codex://doctor, or codex://usage when a prior call failed or availability is uncertain. If Claude asks for a resource server id, use plugin:codex-subagents:codex-subagents, not the mcp__... tool prefix or a stale plain codex-subagents server.",
2644126441
"- Debug tools such as codex_status, codex_doctor, codex_usage_guide, codex_choose_tool, and codex_export_debug_bundle are hidden unless CODEX_SUBAGENTS_ENABLE_DEBUG_TOOLS=1.",
2644226442
"- Legacy/manual tools such as ask_codex, run_agent, run_agents, and old session names are hidden unless CODEX_SUBAGENTS_ENABLE_LEGACY_TOOLS=1.",
2644326443
"",

scripts/link-claude-dev-plugin.mjs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { lstat, mkdir, readFile, realpath, rename, rm, symlink, writeFile } from "node:fs/promises";
1+
import { copyFile, lstat, mkdir, readFile, realpath, rename, rm, symlink, writeFile } from "node:fs/promises";
22
import os from "node:os";
33
import path from "node:path";
44
import { fileURLToPath } from "node:url";
@@ -11,7 +11,12 @@ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
1111
const marketplace = "codex-subagents-local";
1212
const pluginName = manifest.name;
1313
const version = manifest.version;
14+
const backupStamp = new Date().toISOString().replace(/[-:TZ.]/g, "").slice(0, 14);
1415
const installedPluginsPath = path.join(claudeHome, "plugins", "installed_plugins.json");
16+
const backupRoot = path.join(claudeHome, "backups", `codex-subagents-legacy-${backupStamp}`);
17+
const desktopConfigPath =
18+
process.env.CLAUDE_DESKTOP_CONFIG ??
19+
path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
1520
const marketplacePluginPath = path.join(
1621
claudeHome,
1722
"plugins",
@@ -48,8 +53,7 @@ async function resolvedPath(targetPath) {
4853
}
4954

5055
function backupPath(targetPath) {
51-
const stamp = new Date().toISOString().replace(/[-:TZ.]/g, "").slice(0, 14);
52-
return `${targetPath}.backup-${stamp}`;
56+
return `${targetPath}.backup-${backupStamp}`;
5357
}
5458

5559
async function replaceWithSymlink(linkPath, targetPath) {
@@ -112,12 +116,70 @@ async function ensureInstalledPluginsEntry() {
112116
return entry;
113117
}
114118

119+
function legacyBackupName(targetPath) {
120+
return targetPath
121+
.slice(claudeHome.length)
122+
.replace(/^[/\\]+/, "")
123+
.replace(/[/:\\]+/g, "__");
124+
}
125+
126+
async function moveLegacyPath(targetPath) {
127+
assertSafePath(targetPath);
128+
if (!(await pathExists(targetPath))) return false;
129+
if (dryRun) return true;
130+
131+
await mkdir(backupRoot, { recursive: true });
132+
await rename(targetPath, path.join(backupRoot, `${legacyBackupName(targetPath)}.old`));
133+
return true;
134+
}
135+
136+
async function removeLegacyClaudeDesktopServer() {
137+
if (!(await pathExists(desktopConfigPath))) return false;
138+
139+
const data = JSON.parse(await readFile(desktopConfigPath, "utf8"));
140+
const serverConfig = data.mcpServers?.[pluginName];
141+
if (!serverConfig) return false;
142+
143+
const command = typeof serverConfig.command === "string" ? serverConfig.command : "";
144+
const isLegacyCodexSubagents =
145+
command === path.join(root, "dist", "index.js") ||
146+
command.endsWith("/claude-code-codex-subagents/dist/index.js") ||
147+
command.includes("codex-subagents");
148+
if (!isLegacyCodexSubagents) return false;
149+
150+
if (!dryRun) {
151+
await mkdir(backupRoot, { recursive: true });
152+
await copyFile(desktopConfigPath, path.join(backupRoot, "claude_desktop_config.json.before"));
153+
delete data.mcpServers[pluginName];
154+
await writeFile(desktopConfigPath, `${JSON.stringify(data, null, 2)}\n`);
155+
}
156+
return true;
157+
}
158+
159+
async function cleanupLegacyInstallSurfaces() {
160+
const removed = [];
161+
for (const targetPath of [
162+
path.join(claudeHome, "plugins", pluginName),
163+
path.join(claudeHome, "commands", `${pluginName}.md`),
164+
path.join(claudeHome, "plugins", "data", `${pluginName}-inline`),
165+
]) {
166+
if (await moveLegacyPath(targetPath)) removed.push(targetPath);
167+
}
168+
if (await removeLegacyClaudeDesktopServer()) removed.push(desktopConfigPath);
169+
return removed;
170+
}
171+
115172
const marketplaceLink = await replaceWithSymlink(marketplacePluginPath, root);
116173
const cacheLink = await replaceWithSymlink(installPath, root);
117174
const entry = await ensureInstalledPluginsEntry();
175+
const removedLegacyPaths = await cleanupLegacyInstallSurfaces();
118176

119177
console.log(`Marketplace plugin path: ${marketplaceLink.path} -> ${root}`);
120178
console.log(`Installed plugin path: ${cacheLink.path} -> ${root}`);
121179
console.log(`Installed plugin entry: ${entry.installPath}`);
180+
if (removedLegacyPaths.length > 0) {
181+
console.log(`Removed legacy Codex subagents install surfaces: ${removedLegacyPaths.join(", ")}`);
182+
if (!dryRun) console.log(`Legacy backups: ${backupRoot}`);
183+
}
122184
if (dryRun) console.log("Dry run only; no Claude plugin files were modified.");
123185
console.log("Claude Code CLI and Claude Desktop CLI share this ~/.claude plugin install.");

skills/codex-subagents/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Prefer the native Claude-like tools for normal use:
2828
- For independent tasks that can run concurrently, call `codex_task_group` with one task object per workstream. Split by ownership such as API flow, tests, security, performance, UI, docs, or migration risk. Keep tasks concrete and bounded, and set `max_parallel` to the smaller of the useful agent count and `4` unless the user asks for more.
2929
- For multi-turn Codex work, call `codex_task` for the initial prompt and preserve the returned `session_id`. Use `codex_followup` with `mode: "queue"` for ordinary follow-ups, `mode: "wait"` when Claude needs completion, and `mode: "steer"` for active redirection.
3030
- Use `codex_followup` mode `steer` only when the user wants to redirect active work now. It delivers real live steering with Codex `turn/steer` when app-server support is active. Set `interrupt_current: true` only when the active turn should be cancelled and redirected.
31-
- If unsure which path fits, read `codex://usage` before delegating.
31+
- If unsure which path fits, call `codex_task` or `codex_followup` first; use MCP resources only for diagnostics or when a tool response explicitly says to inspect a resource.
3232

3333
Legacy tools such as `ask_codex`, `run_agent`, and old session names are hidden by default. They are exposed only when `CODEX_SUBAGENTS_ENABLE_LEGACY_TOOLS=1` is set for older clients. Tool-callable diagnostics are hidden unless `CODEX_SUBAGENTS_ENABLE_DEBUG_TOOLS=1`; use resources `codex://status`, `codex://doctor`, and `codex://usage` for normal diagnostics.
3434

@@ -56,6 +56,8 @@ Do not use Codex for simple file reads, simple grep/search, or tiny local comman
5656

5757
Read `codex://doctor` or `codex://status` only when diagnosing installation, binary resolution, defaults, or after a failed Codex tool call. Normal delegation should start with `codex_task`, `codex_task_group`, or `codex_followup`.
5858

59+
When using Claude's generic `ReadMcpResourceTool`, the server id for this plugin is `plugin:codex-subagents:codex-subagents`. Do not convert the tool prefix `mcp__plugin_codex-subagents_codex-subagents__...` into `plugin_codex-subagents_codex-subagents`; that underscore form is not a server id. Do not use a plain `codex-subagents` server id if Claude also lists the plugin server, because that indicates a stale direct MCP entry and session resources will not line up with plugin tool calls.
60+
5961
Example `codex_task` arguments for a retained session:
6062

6163
```json

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ const usageGuide = [
7373
"- Use codex_task_group when the work can be split into independent concurrent Codex tasks and Claude wants one combined response with rolled-up per-task findings.",
7474
"- Use codex_followup when Claude already has a session_id from codex_task or codex_task_group and wants to continue, steer, wait on, or cancel that same Codex context. This is a Codex-specific multi-turn extension; wait/cancel correspond to native TaskOutput/TaskStop-style operations.",
7575
"- Set codex_task background true for long-running work so Claude gets a session_id immediately, then use codex_wait_any for several sessions or codex_followup mode wait, steer, or cancel for one session.",
76-
"- Subscribe to or read codex://sessions/{session_id} for background progress milestones and completion state. This is the Codex TaskGet/TaskList-style resource surface. The server emits notifications/resources/updated when meaningful Codex output changes.",
77-
"- Diagnostics are resources by default: read codex://status, codex://doctor, or codex://usage when a prior call failed or availability is uncertain.",
76+
"- Prefer codex_followup mode wait and codex_wait_any for completion. Subscribe to or read codex://sessions/{session_id} only when resource access is available and Claude needs progress milestones or completion state.",
77+
"- Diagnostics are resources by default: read codex://status, codex://doctor, or codex://usage when a prior call failed or availability is uncertain. If Claude asks for a resource server id, use plugin:codex-subagents:codex-subagents, not the mcp__... tool prefix or a stale plain codex-subagents server.",
7878
"- Debug tools such as codex_status, codex_doctor, codex_usage_guide, codex_choose_tool, and codex_export_debug_bundle are hidden unless CODEX_SUBAGENTS_ENABLE_DEBUG_TOOLS=1.",
7979
"- Legacy/manual tools such as ask_codex, run_agent, run_agents, and old session names are hidden unless CODEX_SUBAGENTS_ENABLE_LEGACY_TOOLS=1.",
8080
"",

test/dev-link.mjs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { spawnSync } from "node:child_process";
2-
import { lstat, mkdtemp, readFile, realpath, rm } from "node:fs/promises";
2+
import { lstat, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
33
import os from "node:os";
44
import path from "node:path";
55

@@ -16,6 +16,7 @@ const installPath = path.join(
1616
claudeHome,
1717
`plugins/cache/codex-subagents-local/codex-subagents/${manifest.version}`,
1818
);
19+
const desktopConfigPath = path.join(claudeHome, "Claude", "claude_desktop_config.json");
1920
const marketplacePath = path.join(
2021
claudeHome,
2122
"plugins/marketplaces/codex-subagents-local/plugins/codex-subagents",
@@ -39,13 +40,32 @@ try {
3940
env: {
4041
...process.env,
4142
CLAUDE_HOME: claudeHome,
43+
CLAUDE_DESKTOP_CONFIG: desktopConfigPath,
4244
},
4345
});
4446
const dryRunOutput = [dryRun.stdout, dryRun.stderr].filter(Boolean).join("");
4547
assert(dryRun.status === 0, "dev link dry run should succeed", dryRunOutput);
4648
assert(dryRunOutput.includes("Dry run only"), "dry run should be explicit", dryRunOutput);
4749
assert(!(await pathExists(path.join(claudeHome, "plugins"))), "dry run should not create plugin directories", dryRunOutput);
4850

51+
await mkdir(path.dirname(desktopConfigPath), { recursive: true });
52+
await writeFile(
53+
desktopConfigPath,
54+
`${JSON.stringify({
55+
mcpServers: {
56+
"codex-subagents": {
57+
command: path.join(root, "dist", "index.js"),
58+
args: [],
59+
env: {},
60+
},
61+
unrelated: { command: "/bin/echo" },
62+
},
63+
}, null, 2)}\n`,
64+
);
65+
await mkdir(path.join(claudeHome, "plugins", "data", "codex-subagents-inline"), { recursive: true });
66+
await mkdir(path.join(claudeHome, "commands"), { recursive: true });
67+
await writeFile(path.join(claudeHome, "commands", "codex-subagents.md"), "legacy command\n");
68+
4969
for (const pass of [1, 2]) {
5070
const result = spawnSync("node", ["scripts/link-claude-dev-plugin.mjs"], {
5171
cwd: root,
@@ -54,6 +74,7 @@ try {
5474
env: {
5575
...process.env,
5676
CLAUDE_HOME: claudeHome,
77+
CLAUDE_DESKTOP_CONFIG: desktopConfigPath,
5778
},
5879
});
5980

@@ -67,6 +88,9 @@ try {
6788
marketplacePath,
6889
output,
6990
});
91+
if (pass === 1) {
92+
assert(output.includes("Removed legacy Codex subagents install surfaces"), "legacy cleanup should be reported", output);
93+
}
7094
}
7195

7296
const installed = JSON.parse(
@@ -76,6 +100,15 @@ try {
76100
assert(entry?.installPath === installPath, "installed plugin entry should use the cache symlink", entry);
77101
assert(entry?.gitCommitSha === "dev-symlink", "installed plugin entry should be marked as dev symlink", entry);
78102

103+
const desktopConfig = JSON.parse(await readFile(desktopConfigPath, "utf8"));
104+
assert(!desktopConfig.mcpServers["codex-subagents"], "direct Claude Desktop MCP server should be removed", desktopConfig);
105+
assert(desktopConfig.mcpServers.unrelated, "unrelated Claude Desktop MCP servers should be preserved", desktopConfig);
106+
assert(!(await pathExists(path.join(claudeHome, "commands", "codex-subagents.md"))), "legacy command should be moved out of active Claude paths");
107+
assert(
108+
!(await pathExists(path.join(claudeHome, "plugins", "data", "codex-subagents-inline"))),
109+
"legacy inline plugin data should be moved out of active Claude paths",
110+
);
111+
79112
console.log("Claude dev-link test passed");
80113
} finally {
81114
await rm(claudeHome, { recursive: true, force: true });

0 commit comments

Comments
 (0)