Symptom
After the platform-config-dir migration (#134), a user with a pre-MON-5371 install on macOS or Windows has files in both the legacy ~/.config/google-readonly/ directory and the new os.UserConfigDir/google-readonly/ directory.
Running gro config clear --all followed by gro init does not get them to a clean state:
-
clear --all removes the keychain token, the new-location config.yml, the new-location Drive cache, and the legacy Drive cache.
-
It does not remove the legacy config file (~/.config/google-readonly/config.json or config.yml).
-
On the next gro init, DetectConfigRelocation sees the legacy file is still present, the new dir is missing (we just removed it), so it returns relocOldOnly with CopyNeeded=true, then ApplyConfigRelocation copies the legacy file forward.
-
If the user has independently re-created the new-location file (via, say, an installer that drives gro init in a loop or a partial intermediate run), step 3 instead returns relocBothDivergent and gro init refuses to proceed:
warning: config: shared old/new config diverge: old /Users/x/.config/google-readonly/config.json and new /Users/x/Library/Application Support/google-readonly/config.yml have different settings; reconcile (or delete one) before running gro init
Why this looks like an oversight
runClear --all in internal/cmd/config/config.go explicitly handles both the new and legacy cache dirs:
if cacheDir, cerr := config.CacheDirPath(); cerr == nil {
if rmErr := os.RemoveAll(cacheDir); rmErr != nil {
return fmt.Errorf("removing Drive metadata cache %s: %w", config.ShortenPath(cacheDir), rmErr)
}
fmt.Printf("Removed Drive metadata cache at %s.\n", config.ShortenPath(cacheDir))
}
if legacy, lerr := config.LegacyCacheDir(); lerr == nil {
if rmErr := os.RemoveAll(legacy); rmErr != nil {
return fmt.Errorf("removing legacy Drive cache %s: %w", config.ShortenPath(legacy), rmErr)
}
}
The comment above this block makes the intent clear:
Drive metadata cache: an explicit full reset removes BOTH the current cache dir and the legacy (pre-B2b) one directly — no cache.New(), so no migrate-then-delete dance and no MkdirAll side-effect.
But the equivalent block for the config file removes only the new-location cfgPath. The legacy config dir's config.{json,yml} is never touched. The asymmetry between cache (both removed) and config (only new removed) reads as the config-side cleanup not being updated when MON-5371 introduced platform-specific config dirs (the cache-side cleanup landed in B2b / #130; MON-5371 landed in #134 but didn't extend clear --all).
I think the ApplyConfigRelocation copy-leave-old policy is the right default for init — the legacy file is a recovery point for the user. But for an explicit clear --all ("Also remove config.yml and the Drive metadata cache"), the user is asking for a full reset; leaving the legacy file behind defeats the operation and silently re-creates the divergence trap on the next init.
Proposed fix
In runClear, after os.Remove(cfgPath), also remove the legacy config file. Something like:
if legacyDir, lerr := config.LegacyConfigDir(); lerr == nil {
for _, name := range []string{config.ConfigFileYAML, config.ConfigFile} {
legacyPath := filepath.Join(legacyDir, name)
switch err := os.Remove(legacyPath); {
case err == nil:
fmt.Printf("Removed legacy %s.\n", config.ShortenPath(legacyPath))
case os.IsNotExist(err):
// fine
default:
return fmt.Errorf("removing legacy %s: %w", config.ShortenPath(legacyPath), err)
}
}
}
Notes:
- The OAuth client JSON in the legacy dir is still preserved (consistent with the existing "never removed" guarantee for deployment material).
- A
LegacyConfigDir() helper would need to exist or be added; the package already has LegacyCacheDir() as precedent.
--dry-run would need parallel updates so users see what --all will actually remove before running it.
Impact today
Downstream installers (claude-desktop-mcp PRs #31 Windows + #32 macOS) currently work around this by running gro config clear --all and then os.unlink-ing the legacy config.json themselves before invoking gro init. Moving the cleanup into gro itself would let downstream installers drop that workaround.
Repro
On a fresh macOS box:
- Seed
~/.config/google-readonly/config.json with {} and ~/Library/Application Support/google-readonly/config.yml with credential_ref: foo/bar.
gro config clear --all — succeeds.
ls ~/.config/google-readonly/ — config.json is still there.
- Touch
~/Library/Application Support/google-readonly/config.yml with anything → gro init → divergence warning.
Happy to PR if useful.
Symptom
After the platform-config-dir migration (#134), a user with a pre-MON-5371 install on macOS or Windows has files in both the legacy
~/.config/google-readonly/directory and the newos.UserConfigDir/google-readonly/directory.Running
gro config clear --allfollowed bygro initdoes not get them to a clean state:clear --allremoves the keychain token, the new-locationconfig.yml, the new-location Drive cache, and the legacy Drive cache.It does not remove the legacy config file (
~/.config/google-readonly/config.jsonorconfig.yml).On the next
gro init,DetectConfigRelocationsees the legacy file is still present, the new dir is missing (we just removed it), so it returnsrelocOldOnlywithCopyNeeded=true, thenApplyConfigRelocationcopies the legacy file forward.If the user has independently re-created the new-location file (via, say, an installer that drives
gro initin a loop or a partial intermediate run), step 3 instead returnsrelocBothDivergentandgro initrefuses to proceed:Why this looks like an oversight
runClear --allininternal/cmd/config/config.goexplicitly handles both the new and legacy cache dirs:The comment above this block makes the intent clear:
But the equivalent block for the config file removes only the new-location
cfgPath. The legacy config dir'sconfig.{json,yml}is never touched. The asymmetry between cache (both removed) and config (only new removed) reads as the config-side cleanup not being updated when MON-5371 introduced platform-specific config dirs (the cache-side cleanup landed in B2b / #130; MON-5371 landed in #134 but didn't extendclear --all).I think the
ApplyConfigRelocationcopy-leave-old policy is the right default forinit— the legacy file is a recovery point for the user. But for an explicitclear --all("Also remove config.yml and the Drive metadata cache"), the user is asking for a full reset; leaving the legacy file behind defeats the operation and silently re-creates the divergence trap on the next init.Proposed fix
In
runClear, afteros.Remove(cfgPath), also remove the legacy config file. Something like:Notes:
LegacyConfigDir()helper would need to exist or be added; the package already hasLegacyCacheDir()as precedent.--dry-runwould need parallel updates so users see what--allwill actually remove before running it.Impact today
Downstream installers (
claude-desktop-mcpPRs #31 Windows + #32 macOS) currently work around this by runninggro config clear --alland thenos.unlink-ing the legacyconfig.jsonthemselves before invokinggro init. Moving the cleanup into gro itself would let downstream installers drop that workaround.Repro
On a fresh macOS box:
~/.config/google-readonly/config.jsonwith{}and~/Library/Application Support/google-readonly/config.ymlwithcredential_ref: foo/bar.gro config clear --all— succeeds.ls ~/.config/google-readonly/—config.jsonis still there.~/Library/Application Support/google-readonly/config.ymlwith anything →gro init→ divergence warning.Happy to PR if useful.