Skip to content

fix(omo-codex): fail on unreadable config.toml instead of overwriting it (fixes #5751)#5753

Open
MoerAI wants to merge 1 commit into
code-yeongyu:devfrom
MoerAI:fix/codex-installer-fail-on-unreadable-config
Open

fix(omo-codex): fail on unreadable config.toml instead of overwriting it (fixes #5751)#5753
MoerAI wants to merge 1 commit into
code-yeongyu:devfrom
MoerAI:fix/codex-installer-fail-on-unreadable-config

Conversation

@MoerAI

@MoerAI MoerAI commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

The omo-codex installer could overwrite an existing config.toml when the file exists but cannot be read. updateCodexConfig() now reads the config directly and only treats a genuinely-absent file (ENOENT) as empty; any other read error is re-thrown before a single byte is written.

Fixes #5751.

Root Cause

updateCodexConfig() probed for an existing config with a local exists() helper:

let config = ""
if (await exists(input.configPath)) config = await readFile(input.configPath, "utf8")
// ...
async function exists(path: string): Promise<boolean> {
  try { await readFile(path, "utf8"); return true }
  catch (error) { if (error instanceof Error) return false; return false }
}

exists() swallowed every readFile() error and returned false. A present-but-unreadable config.toml (EACCES/EPERM, or any non-ENOENT read failure) was therefore treated as absent, so the function started from an empty config and writeFileAtomic() replaced the user's file, dropping its contents.

Changes

File Change
packages/omo-codex/src/install/codex-config-toml.ts Read the config directly; catch only ENOENT (use empty config), re-throw all other read errors before any write. Remove the now-unused exists() helper. Reuses the existing isNodeErrorWithCode guard from codex-cache-fs.
packages/omo-codex/src/install/codex-config-toml.test.ts Two regression tests: a cross-platform case (an unreadable path surfaces the read error instead of overwriting) and a POSIX chmod 0o200 case asserting the original file is preserved.

Net source change: +6 / -9 (one import, the read guard, minus the dead helper).

Reproduction (before fix)

The new test against the unmodified code shows the installer reaches writeFileAtomic and attempts to overwrite the existing path:

).rejects.toThrow("EISDIR")
error: expect(received).toThrow(expected)
Expected substring: "EISDIR"
Received message: "EPERM: operation not permitted, rename
'...\.tmp-config.toml-7968-...' -> '...\config.toml'"
(fail) codex-config-toml > ...surfaces the read error instead of overwriting from an empty config

chmod cannot make a file unreadable on Windows, so the unreadable case is exercised cross-platform via a directory config.toml (so readFile throws EISDIR), plus a POSIX chmod 0o200 test that is skipped on Windows.

Verification (after fix)

(pass) codex-config-toml > ...surfaces the read error instead of overwriting from an empty config
(skip) codex-config-toml > ...rejects and preserves the original file   # POSIX-only chmod case
 22 pass
 1 skip
 0 fail
Ran 23 tests across 1 file.

Test

  • Regression: packages/omo-codex/src/install/codex-config-toml.test.ts (2 new cases) - 22 pass / 1 skip (POSIX-only chmod case) / 0 fail
  • Typecheck: tsgo --noEmit -p packages/omo-codex/tsconfig.json - clean (exit 0)

Fixes #5751


Summary by cubic

Prevents the omo-codex installer from overwriting an existing config.toml when the file exists but can’t be read. updateCodexConfig() now treats only ENOENT as “missing” and fails fast on other read errors. Fixes #5751.

  • Bug Fixes
    • Read the config directly; catch only ENOENT, re-throw all other read errors before any write.
    • Removed the exists() probe that swallowed read errors and caused unintended overwrites.
    • Added regression tests for unreadable paths (cross‑platform) and POSIX chmod to ensure the original file is preserved.

Written for commit e4b783c. Summary will update on new commits.

Review in cubic

… it (fixes code-yeongyu#5751)

updateCodexConfig() probed for an existing config.toml with exists(), which
caught every readFile() error and returned false. A present-but-unreadable
config.toml (EACCES/EPERM, or any non-ENOENT read error) was treated as
absent, so the installer started from an empty config and writeFileAtomic()
overwrote the user's file, dropping its contents.

Read the config directly and catch only ENOENT (genuinely absent, use an
empty config); re-throw every other read error before any write. The unused
exists() helper is removed.

Verified: new regression test goes RED (pre-fix reaches writeFileAtomic and
attempts an overwrite) then GREEN (the read error is re-thrown before any
write); codex-config-toml.test.ts 22 pass / 1 skip (POSIX chmod case) / 0
fail; tsgo --noEmit clean.
@MoerAI

MoerAI commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

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

@github-actions github-actions Bot added the lazycodex Codex (LazyCodex) edition: packages/omo-codex label Jun 30, 2026

@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: e4b783c555

ℹ️ 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".

Comment on lines +39 to +42
try {
config = await readFile(input.configPath, "utf8")
} catch (error) {
if (!(isNodeErrorWithCode(error) && error.code === "ENOENT")) throw error

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 Badge Regenerate the Codex installer bundle

This source-only fix is bypassed by the stable installer entrypoint in a fresh checkout: packages/omo-codex/scripts/install-local.mjs imports scripts/install-dist/install-local.mjs whenever that checked-in bundle exists, and the bundle still contains the removed if (await exists(input.configPath)) config = await readFile(...) path around its updateCodexConfig(). As a result, node packages/omo-codex/scripts/install-local.mjs install can still treat non-ENOENT read failures as an absent config and overwrite user data; run bun run build:codex-install and commit the regenerated bundle so the fix reaches the script consumers.

Useful? React with 👍 / 👎.

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

Labels

lazycodex Codex (LazyCodex) edition: packages/omo-codex

1 participant