digitalocean / digitalocean/doctl

auth switch silently writes to a stale path when config carries a persisted config: key

Open
#1,882 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
3.4k
Forks
496
Avg merge
1d 2h
Merged PRs (30d)
31

Description

## Description

`doctl auth switch --context ` prints `Now using context [] by default` but **silently fails to persist** when the config file contains a top-level `config:` key that points somewhere other than the file actually in use. The switch writes the updated config to that *stale* path; the live config is never updated, and no error is shown.

This is self-inflicted: `writeConfig()` marshals `viper.AllSettings()`, which includes the bound `config` flag, so **every write bakes a top-level `config:` key into the config file**. It surfaces whenever a config is relocated or copied between machines — e.g. copying `~/Library/Application Support/doctl/config.yaml` from macOS into a Linux container's `~/.config/doctl/` — because the baked-in path no longer matches where the file now lives.

## Steps to reproduce

```bash
# Bake a stale config: path into the config (simulates a config copied from another machine)
sed -i 's#^config: .*#config: /tmp/doctl-STALE.yaml#' ~/.config/doctl/config.yaml
grep '^context:' ~/.config/doctl/config.yaml # -> context: default

doctl auth switch --context myteam
# prints: Now using context [myteam] by default

grep '^context:' ~/.config/doctl/config.yaml # STILL context: default (not persisted)
grep '^context:' /tmp/doctl-STALE.yaml # context: myteam (written to the stale path)
doctl account get # still the old context's account
```

## Root cause

1. `writeConfig()` ([`commands/auth.go`](https://github.com/digitalocean/doctl/blob/main/commands/auth.go)) does `yaml.Marshal(viper.AllSettings())`. `AllSettings()` includes `config` (bound via `BindPFlag("config", …)` in `commands/doit.go`), so a top-level `config: ` is persisted into the file.
2. On the next run, `viper.ReadInConfig()` loads that key. In viper's precedence a **config-file value outranks a flag's default**, so `viper.GetString("config")` returns the file's stored path, not the file in use.
3. `defaultConfigFileWriter()` uses that value as the write target (`os.Create(viper.GetString("config"))`). If the stored path is stale, the write lands there and the live file is untouched — while `RunAuthSwitch` has already printed success.

Passing `--config ` masks the bug, because an explicitly-set flag outranks the config-file value.

## Expected

`auth switch` should write to the config file doctl actually loaded (`viper.ConfigFileUsed()`), not to a path read back out of that file.

## Environment

- doctl 1.163.0 (also present on `main`)
- Reproduces on any OS; most likely to bite when a config is copied between machines.

## Related

Sibling of #1816 — same root cause (`writeConfig` persisting `viper.AllSettings()` keys), different symptom.

A fix + regression test is up in the PR linked below.

Contributor guide

Open the contributing guide

Research direction

Start with commands/auth.go's writeConfig and defaultConfigFileWriter, then inspect the config flag binding in commands/doit.go and reproduce the stale-path steps. Add a regression test for auth switch and verify that the live config file's context changes instead of the stale path being written.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.