Skip to content

config clear --all removes the legacy Drive cache but not the legacy config dir #137

@rianjs

Description

@rianjs

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:

  1. clear --all removes the keychain token, the new-location config.yml, the new-location Drive cache, and the legacy Drive cache.

  2. It does not remove the legacy config file (~/.config/google-readonly/config.json or config.yml).

  3. 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.

  4. 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:

  1. Seed ~/.config/google-readonly/config.json with {} and ~/Library/Application Support/google-readonly/config.yml with credential_ref: foo/bar.
  2. gro config clear --all — succeeds.
  3. ls ~/.config/google-readonly/config.json is still there.
  4. Touch ~/Library/Application Support/google-readonly/config.yml with anything → gro init → divergence warning.

Happy to PR if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions