Skip to content

fix(installer): keep --refresh off the configs of unconfigured agents (#1870) - #1873

Open
L4XB wants to merge 1 commit into
colbymchenry:mainfrom
L4XB:fix/1870-detect-no-backup
Open

L4XB wants to merge 1 commit into
colbymchenry:mainfrom
L4XB:fix/1870-detect-no-backup

Conversation

@L4XB

@L4XB L4XB commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Closes #1870

The defect

readJsonFile in src/installer/targets/shared.ts copied an unparseable
file to <path>.backup and warned that it was about to be overwritten.
That is the right thing to do immediately before a write — but every
target's detect() reads its agent's config through the same helper,
and install --refresh detects every target precisely so it can skip
the ones CodeGraph was never installed into.

refreshTargets documents that contract in its own comment — "targets
that aren't alreadyConfigured are skipped untouched" — and the CLI flag
help says "for already-configured agents only". Detection was quietly
breaking it: the skip decision itself wrote a file.

So the reported setup — an empty ~/.gemini/config/mcp_config.json beside
an Antigravity install CodeGraph does not manage — produced a 0-byte
mcp_config.json.backup and a warning about an overwrite that never came.

The fix

readJsonFile is now a pure read: missing or unparseable both return {},
silently. Preserving an unparseable config moves to writeJsonFile, the
single place a target replaces a JSON config, so the backup still happens
everywhere it mattered and only there. Two read-then-decide paths that used
to leave a stray backup when they decided not to write — the
legacy-entry cleanups and the uninstall sweeps — no longer do.

AgentTarget.detect now carries the read-only contract in its doc comment
so a future target does not reintroduce this.

Not changed on purpose

The issue's second suggestion — treat an empty file as {} with no warning
— is deliberately left out of this PR. After this change it only affects the
write path: a real codegraph install into an agent whose config is a 0-byte
file still warns and still writes a 0-byte .backup. Suppressing the copy
when there is genuinely nothing to preserve seems right to me, and the JSONC
targets already do exactly that (parseConfig: if (!text.trim()) return {}),
but it changes what a real install does rather than what detection does, so
it felt like your call rather than a drive-by. Happy to add it here if you want it.

Evidence

Built main (58c07e8) and this branch, ran the reported command against a
sandboxed HOME containing an empty ~/.gemini/config/mcp_config.json and
no CodeGraph entry anywhere.

Before — main @ 58c07e8

$ codegraph install --refresh --location global
  Warning: Could not parse mcp_config.json: Unexpected end of JSON input
  A backup will be created before overwriting.
All configured agent surfaces are already current.

$ ls -l ~/.gemini/config
-rw-r--r--  0  mcp_config.json
-rw-r--r--  0  mcp_config.json.backup      <-- created by the detection pass

After — this branch

$ codegraph install --refresh --location global
All configured agent surfaces are already current.

$ ls -l ~/.gemini/config
-rw-r--r--  0  mcp_config.json

The backup still happens where it should. Same sandbox, config set to
{ half a config, running a real install instead of a refresh:

$ codegraph install --yes --target antigravity --location global
  Warning: Could not parse mcp_config.json: Expected property name or '}' in JSON at position 2
  A backup will be created before overwriting.
◆  Antigravity IDE: Updated ~/.gemini/config/mcp_config.json

$ cat ~/.gemini/config/mcp_config.json.backup
{ half a config

Tests

Three new tests in __tests__/installer-targets.test.ts under
Installer — detection never writes (#1870): a refresh over an
unconfigured agent's unparseable config, detect() on its own, and a
guard that an install which really overwrites one still backs it up first.

The two detection tests fail on the merge base and pass here:

$ git checkout 58c07e8 && <this branch's test file> && npx vitest run \
    __tests__/installer-targets.test.ts -t "detection never writes"
 × a refresh leaves an unconfigured agent's unparseable config alone — no backup, no warning
   → expected [ 'mcp_config.json', …(1) ] to deeply equal [ 'mcp_config.json' ]
 × detect() on its own touches nothing
   → expected [ 'mcp_config.json', …(1) ] to deeply equal [ 'mcp_config.json' ]
 Tests  2 failed | 1 passed | 248 skipped (251)
$ npx vitest run __tests__/installer-targets.test.ts -t "detection never writes"   # this branch
 Tests  3 passed | 248 skipped (251)

Full suite, macOS:

$ npm test          # Node 26.8.2
 Test Files  253 passed | 16 skipped (269)
      Tests  4464 passed | 192 skipped (4656)

On Node 24.2.0 the same suite ends with one Worker exited unexpectedly
in extraction.test.ts — that reproduces identically on main @ 58c07e8
here (both 252 files passed, 1 error), and the file passes when run on its
own, so it is this machine under parallel load rather than anything in this
change. Test counts line up exactly: main 4445 passed, this branch 4448 —
the three new tests and nothing else.

npx tsc --noEmit is clean. A CHANGELOG entry is under [Unreleased]
### Fixes#### MCP / indexing.

Not validated on Windows (no VM here); the change is path-independent fs
plumbing, and the reporter's Windows 11 repro is the same code path.

…colbymchenry#1870)

`readJsonFile` copied an unparseable file to `<path>.backup` and warned
that it was about to be overwritten. That is the right thing to do just
before a write, but every target's `detect()` reads its agent's config
through the same helper — and `install --refresh` detects every target
in order to skip the ones codegraph was never installed into.

So an empty `~/.gemini/config/mcp_config.json`, sitting beside an
Antigravity install codegraph does not manage, got a 0-byte
`mcp_config.json.backup` and a warning about an overwrite that never
came. Detection is meant to be read-only.

`readJsonFile` is now a pure read. Preserving an unparseable config
moves to `writeJsonFile`, the one place a target replaces a JSON config
— so the backup still happens wherever it mattered, and only there.
Reads that decide not to write (the legacy-entry cleanups, the uninstall
sweeps) no longer leave one behind either.

Unchanged on purpose: an install that really does overwrite an empty
config still backs it up. Skipping the copy when there is nothing to
preserve is a separate call.

This branch has not been deployed

No deployments
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.

install --refresh creates .backup files in configs of agents that aren't configured

1 participant