Skip to content

Fix duplicate config section when writing a second key#314

Merged
gtsiolis merged 1 commit into
mainfrom
fix-config-duplicate-section
Jun 16, 2026
Merged

Fix duplicate config section when writing a second key#314
gtsiolis merged 1 commit into
mainfrom
fix-config-duplicate-section

Conversation

@gtsiolis

@gtsiolis gtsiolis commented Jun 16, 2026

Copy link
Copy Markdown
Member

Summary

config.Set persists individual keys to config.toml through setInFile, which edits the file surgically to preserve user comments and formatting. It had a latent corruption bug: when the target field was absent, it always appended a brand-new [section] header — even when that section already existed. Writing a second, distinct key to an existing section produced a duplicate TOML table, which the parser rejects, so every subsequent config load failed.

On a field-match miss, setInFile unconditionally appended a new section:

content = strings.TrimRight(content, "\n") + "\n\n[" + section + "]\n" + assignment + "\n"

Today only cli.update_skipped_version is ever written, so the duplicate-header path is never hit — the bug is latent. But as soon as a second [cli] key is introduced (e.g. an update-notification cadence setting), the sequence becomes:

  1. lstk writes cli.update_skipped_version → appends [cli] (first time, fine).
  2. lstk later writes cli.notify_cadence → field absent → appends a second [cli] header.
  3. Next startup: viper.ReadInConfig / toml.Unmarshal fails with toml: table cli already exists.

The user's config.toml is now unparseable and every command fails until the file is hand-repaired.

Before (corrupted output):

[cli]
update_skipped_version = "v1.0.0"

[cli]
notify_cadence = 'minor'

After (fixed):

[cli]
notify_cadence = 'minor'
update_skipped_version = "v1.0.0"

When the field is absent but the [section] header already exists, insert the new field under the existing header instead of appending a duplicate table. A fresh [section] is appended only when the section does not yet exist. Behavior for existing single-field configs is unchanged; only the previously-broken "second field in an existing section" path differs.

@gtsiolis gtsiolis added semver: patch docs: skip Pull request does not require documentation changes labels Jun 16, 2026
@gtsiolis gtsiolis self-assigned this Jun 16, 2026
setInFile() appended a new [section] header whenever the target field was
absent. Writing a second distinct key to a section that already existed
(e.g. a second [cli] field) therefore produced a duplicate TOML table.
TOML forbids duplicate tables, so the next config load failed with
"table cli already exists", corrupting the user's config.

Insert the field under the existing section header when that section is
already present; only append a new header when the section is absent.
Add a regression test covering a second key in an existing section.
@gtsiolis gtsiolis force-pushed the fix-config-duplicate-section branch from 7ef9ba4 to a88175a Compare June 16, 2026 13:15
@gtsiolis

Copy link
Copy Markdown
Member Author

Thanks for taking a look, @anisaoshafi! 🍿

@gtsiolis gtsiolis merged commit 94f619c into main Jun 16, 2026
13 checks passed
@gtsiolis gtsiolis deleted the fix-config-duplicate-section branch June 16, 2026 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs: skip Pull request does not require documentation changes semver: patch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants