Skip to content

Preserve existing Codex agent TOMLs on reinstall#4711

Open
tully-8888 wants to merge 1 commit into
code-yeongyu:devfrom
tully-8888:fix/preserve-existing-codex-agents
Open

Preserve existing Codex agent TOMLs on reinstall#4711
tully-8888 wants to merge 1 commit into
code-yeongyu:devfrom
tully-8888:fix/preserve-existing-codex-agents

Conversation

@tully-8888

@tully-8888 tully-8888 commented Jun 3, 2026

Copy link
Copy Markdown

Summary

  • Preserve existing CODEX_HOME/agents/*.toml files instead of replacing them during Codex/LazyCodex reinstalls.
  • Materialize existing Unix symlinked agent TOMLs into regular user-owned files before refreshing the stable marketplace snapshot, so local edits survive snapshot rewrites.
  • Keep preserved user files out of .installed-agents.json so cleanup does not delete them, while still registering their [agents.*] config entries.
  • Mirror the behavior in the packaged packages/omo-codex/scripts installer path and update ultrawork docs.

Refs code-yeongyu/lazycodex#18.

Verification

  • bun run typecheck
  • bun test src/cli/install-codex/link-cached-plugin-agents.test.ts src/cli/install-codex/preserve-existing-codex-agents.test.ts
  • node --test packages/omo-codex/scripts/install-agent-links.test.mjs

Notes

bun run test:codex currently stops before reaching the Codex tests because build:lsp-tools-mcp runs npm --prefix packages/lsp-tools-mcp ci, but that package has no package-lock.json in this checkout.


Summary by cubic

Preserves existing agent TOMLs in CODEX_HOME/agents during Codex/LazyCodex reinstalls and marketplace updates so local edits aren’t overwritten. Converts old Unix symlinked agents to user-owned files and only installs missing agents.

  • Bug Fixes
    • Materialize existing symlinked *.toml into regular files before refreshing the marketplace snapshot.
    • Install agents only if missing: symlink on Unix, copy on Windows; never overwrite existing user files.
    • Record only managed agent files in .installed-agents.json so preserved user files aren’t deleted on cleanup.
    • Applied in both the CLI installer and packages/omo-codex/scripts; updated ultrawork docs and added tests.

Written for commit 1f981f7. Summary will update on new commits.

Review in cubic

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA. Thank you! ✅
Posted by the CLA Assistant Lite bot.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f981f7b8d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const materialized: string[] = []
const entries = await readdir(agentsDir, { withFileTypes: true })
for (const entry of entries) {
if (!entry.name.endsWith(".toml")) continue

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 Limit preservation to OMO-managed agent TOMLs

When a user already has their own CODEX_HOME/agents/custom.toml symlink, this loop treats it the same as an OMO-installed agent and the later rm/writeFile replaces the symlink with a regular copy, breaking dotfiles-managed or otherwise user-managed Codex agents. Both installer paths call this preservation step before linking OMO agents, so it should filter to the bundled/managed agent names or verify the symlink target is an OMO marketplace snapshot before materializing it.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

5 issues found across 10 files

Confidence score: 2/5

  • There is a concrete data-loss risk in src/cli/install-codex/preserve-existing-codex-agents.ts and packages/omo-codex/scripts/install/preserve-existing-agents.mjs: the existing symlink is removed before the replacement file is safely written, so write failures can erase preserved agent TOML.
  • exists() in src/cli/install-codex/preserve-existing-codex-agents.ts swallows all lstat errors, which can hide real filesystem problems and silently skip preservation behavior when users most need it.
  • materializeExistingCodexAgentFiles is currently unguarded in src/cli/install-codex/install-codex.ts, so normal FS exceptions (permissions/races) can crash install; combined with the LinkedAgent.target semantic mismatch in src/cli/install-codex/link-cached-plugin-agents.ts, this creates meaningful regression risk.
  • Pay close attention to src/cli/install-codex/preserve-existing-codex-agents.ts, packages/omo-codex/scripts/install/preserve-existing-agents.mjs, src/cli/install-codex/install-codex.ts, src/cli/install-codex/link-cached-plugin-agents.ts - preservation flow can lose user config, mask FS failures, or fail during install.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/cli/install-codex/preserve-existing-codex-agents.ts">

<violation number="1" location="src/cli/install-codex/preserve-existing-codex-agents.ts:17">
P1: Symlink is removed before replacement file is safely written, risking loss of agent config on write failure</violation>

<violation number="2" location="src/cli/install-codex/preserve-existing-codex-agents.ts:35">
P2: `exists()` helper swallows all lstat errors, masking real filesystem failures and silently skipping preservation logic.</violation>
</file>

<file name="src/cli/install-codex/link-cached-plugin-agents.ts">

<violation number="1" location="src/cli/install-codex/link-cached-plugin-agents.ts:34">
P2: `LinkedAgent.target` has mixed semantics depending on `managed` flag: it is the bundled source path when `managed=true`, but the destination (`linkPath`) when `managed=false`. This silently breaks the contract that `target` represents the source artifact.</violation>
</file>

<file name="src/cli/install-codex/install-codex.ts">

<violation number="1" location="src/cli/install-codex/install-codex.ts:83">
P2: `materializeExistingCodexAgentFiles` call is unguarded: permission errors, race-condition ENOENT on `readdir`/`lstat`, and other FS exceptions can crash the entire install. Wrap it in a try-catch so agent-preservation failures don't hard-block the installer.</violation>
</file>

<file name="packages/omo-codex/scripts/install/preserve-existing-agents.mjs">

<violation number="1" location="packages/omo-codex/scripts/install/preserve-existing-agents.mjs:19">
P2: Don't remove the old agent symlink before the replacement file is safely written; a write failure here can erase the user's preserved TOML.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

if (!stat.isSymbolicLink()) continue

const content = await readExistingSymlinkContent(agentPath)
await rm(agentPath, { force: true })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Symlink is removed before replacement file is safely written, risking loss of agent config on write failure

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/install-codex/preserve-existing-codex-agents.ts, line 17:

<comment>Symlink is removed before replacement file is safely written, risking loss of agent config on write failure</comment>

<file context>
@@ -0,0 +1,42 @@
+    if (!stat.isSymbolicLink()) continue
+
+    const content = await readExistingSymlinkContent(agentPath)
+    await rm(agentPath, { force: true })
+    if (content === null) continue
+
</file context>

}
}

async function exists(path: string): Promise<boolean> {

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: exists() helper swallows all lstat errors, masking real filesystem failures and silently skipping preservation logic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/install-codex/preserve-existing-codex-agents.ts, line 35:

<comment>`exists()` helper swallows all lstat errors, masking real filesystem failures and silently skipping preservation logic.</comment>

<file context>
@@ -0,0 +1,42 @@
+  }
+}
+
+async function exists(path: string): Promise<boolean> {
+  try {
+    await lstat(path)
</file context>

const installed = platform === "win32"
? await copyIfMissing(linkPath, agentPath)
: await symlinkIfMissing(linkPath, agentPath)
linked.push({ name: basename(agentPath), path: linkPath, target: installed.target, managed: installed.managed })

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: LinkedAgent.target has mixed semantics depending on managed flag: it is the bundled source path when managed=true, but the destination (linkPath) when managed=false. This silently breaks the contract that target represents the source artifact.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/install-codex/link-cached-plugin-agents.ts, line 34:

<comment>`LinkedAgent.target` has mixed semantics depending on `managed` flag: it is the bundled source path when `managed=true`, but the destination (`linkPath`) when `managed=false`. This silently breaks the contract that `target` represents the source artifact.</comment>

<file context>
@@ -27,16 +28,14 @@ export async function linkCachedPluginAgents(input: {
+    const installed = platform === "win32"
+      ? await copyIfMissing(linkPath, agentPath)
+      : await symlinkIfMissing(linkPath, agentPath)
+    linked.push({ name: basename(agentPath), path: linkPath, target: installed.target, managed: installed.managed })
   }
   await writeManifest(
</file context>

installed.push(plugin)
}

await materializeExistingCodexAgentFiles(codexHome)

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: materializeExistingCodexAgentFiles call is unguarded: permission errors, race-condition ENOENT on readdir/lstat, and other FS exceptions can crash the entire install. Wrap it in a try-catch so agent-preservation failures don't hard-block the installer.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/install-codex/install-codex.ts, line 83:

<comment>`materializeExistingCodexAgentFiles` call is unguarded: permission errors, race-condition ENOENT on `readdir`/`lstat`, and other FS exceptions can crash the entire install. Wrap it in a try-catch so agent-preservation failures don't hard-block the installer.</comment>

<file context>
@@ -79,6 +80,7 @@ export async function runCodexInstaller(options: CodexInstallOptions = {}): Prom
     installed.push(plugin)
   }
 
+  await materializeExistingCodexAgentFiles(codexHome)
   const agentSourceRoots = await agentSourceRootsForInstall({
     codexHome,
</file context>
Suggested change
await materializeExistingCodexAgentFiles(codexHome)
try {
await materializeExistingCodexAgentFiles(codexHome)
} catch (error) {
log(`Warning: could not materialize existing agent files: ${error instanceof Error ? error.message : String(error)}`)
}

if (!stat.isSymbolicLink()) continue;

const content = await readExistingSymlinkContent(agentPath);
await rm(agentPath, { force: true });

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: Don't remove the old agent symlink before the replacement file is safely written; a write failure here can erase the user's preserved TOML.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/omo-codex/scripts/install/preserve-existing-agents.mjs, line 19:

<comment>Don't remove the old agent symlink before the replacement file is safely written; a write failure here can erase the user's preserved TOML.</comment>

<file context>
@@ -0,0 +1,35 @@
+		if (!stat.isSymbolicLink()) continue;
+
+		const content = await readExistingSymlinkContent(agentPath);
+		await rm(agentPath, { force: true });
+		if (content === null) continue;
+
</file context>

@tully-8888

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant