Technology · PRACTICAL GUIDE
TOML says a table was declared twice? Check the key path before deleting a header
Quoted names, dotted keys and generated sections can collide even when the text looks different. Preserve the original, verify the parsed tree and test the next generated file.

A duplicate-table error names a semantic path, not necessarily two identical lines. Preserve the file and identify its writer before deleting headers, renaming keys or rerunning an installer.
1. Preserve the exact file and its writer
Copy the original configuration before editing. Record the exact path the application reads, parser/Python version, complete error and whether the file is hand-maintained, merged or generated. The reported line often identifies the second declaration; search earlier declarations that could own the same path rather than deleting that line immediately.
Pause the failing generation step while inspecting its output. Keep managed and user-owned values separate in your notes. Do not post an entire configuration containing credentials to an online validator; make a redacted, minimal reproduction that preserves the conflicting key structure.
2. Compare semantic path parts, not header strings
In our CPython3.12.14 fixtures, [service.api] followed by [service."api"] failed: quoting the same path component did not create a new table. By contrast, [service."api.v1"] and [service.api.v1] parsed as different trees—the first has a literal key api.v1, while the second has nested api then v1. Renaming or moving quotes can therefore change the application's configuration, not just formatting.
Write down each path component and the intended value beneath it. Do not split every header at every dot with a text search: a dot inside a quoted component is part of its name. Whitespace or equivalent quoted spelling is not a safe uniqueness test for a generator.
3. Identify how the path was already defined
The same fixtures rejected service=1 followed by [service.api], and rejected service.api.port=8000 followed by [service.api]. An inline service={api={port=8000}} followed by an attempt to extend [service.api] also failed. These are value/table or declaration conflicts; changing indentation does not repair their meaning.
An implicit parent is different. [service.api] with a port, followed by [service] with a name, parsed successfully in our test. For two genuinely repeated service.api sections, one consolidated section containing port and host parsed successfully. Decide which declaration owns the table and preserve every required value; do not implement an invented last-one-wins policy or delete an entire block blindly.
4. Validate the tree, then test the next write
Parse a candidate copy with the application's supported parser. In Python3.11+, tomllib.load reads a binary-opened file; tomllib.loads reads a string. It validates TOML syntax, not your application's accepted options, and it does not write TOML. Our eight fixtures ran on CPython3.12.14 with TOML1.0 rules; Python3.15 adds TOML1.1 support, so keep version differences explicit.
Check required key paths, value types and values in the parsed result, then run the application's own safe configuration check. If a generator owns the file, run it twice against a disposable copy and confirm both outputs parse and retain intended user values without new duplicate tables. Only then replace the live file with a backup available. A one-time manual repair is not complete if the next generation corrupts it again; fix the writer or use its documented corrected version.
