Skip to content

fix: return real defaults when codeowners.toml fails to parse - #184

Open
asyncawaitpromise wants to merge 2 commits into
mainfrom
fix/config-partial-parse
Open

fix: return real defaults when codeowners.toml fails to parse#184
asyncawaitpromise wants to merge 2 commits into
mainfrom
fix/config-partial-parse

Conversation

@asyncawaitpromise

@asyncawaitpromise asyncawaitpromise commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Summary / Background

ReadConfig handed the same default Config to the TOML parser and to its own
error paths. go-toml dereferences a non-nil pointer in place rather than
allocating a replacement (unmarshaler.go, the pointer walk in
walkTable), so a file which failed halfway left its partially parsed
values in the instance the error path then returned.

The caller logs using default config and carries on, with whatever the parser
managed to read before it failed.

Why it matters

A malformed codeowners.toml could turn enforcement off, or widen admin bypass,
while the logs said defaults were in force. The config is a security boundary, so
an error path has to return values that never reflect anything read from the file.

The fix

A fresh instance per call. The parser gets its own, and every error path builds
another.

The nil-section fixups go with it. They only ever ran on the success path, and on
that path they were unreachable: TOML cannot express a null table, so a section
which starts non-nil cannot come back nil.

Note that a shallow struct copy is not enough here. The pointer fields would
still be shared, which is exactly what go-toml merges into.

Verification

A test parses a file whose [enforcement] and [admin_bypass] sections are both
read successfully before a later line fails to parse, then asserts every returned
field equals the real default. Reverting the fix turns it red.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the 'Approval Retention' feature, which allows users to configure whether existing approvals are retained across specific types of changes, such as whitespace, comments, formatting, string literals, or renames. The implementation includes updates to the configuration parser, new helper methods for flag resolution, comprehensive unit and integration tests, and updated documentation in the README. Since there are no review comments, I have no additional feedback to provide.

@asyncawaitpromise
asyncawaitpromise force-pushed the fix/config-partial-parse branch from 0572660 to 2c52723 Compare August 19, 2026 00:50
@asyncawaitpromise asyncawaitpromise changed the title fix/config partial parse fix: return real defaults when codeowners.toml fails to parse Aug 19, 2026
@asyncawaitpromise
asyncawaitpromise changed the base branch from main to feat/approval-retention-config August 19, 2026 08:11
@asyncawaitpromise
asyncawaitpromise marked this pull request as ready for review August 20, 2026 09:19
@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
internal/config/config.go Separates the TOML decode target from freshly constructed error-path defaults, preventing partially parsed policy values from escaping.
internal/config/config_test.go Adds a malformed-config regression test that exercises and compares all current Config fields against pristine defaults.
README.md Updates the displayed test coverage percentage.
internal/git/diff_test.go Applies a formatting-only alignment adjustment to an existing expected map.

Reviews (2): Last reviewed commit: "fix: return real defaults when codeowner..." | Re-trigger Greptile

Comment thread internal/config/config_test.go Outdated
@asyncawaitpromise
asyncawaitpromise marked this pull request as draft August 21, 2026 21:33
@asyncawaitpromise
asyncawaitpromise force-pushed the feat/approval-retention-config branch from b313dc9 to 86cc504 Compare August 24, 2026 20:59
@asyncawaitpromise
asyncawaitpromise force-pushed the fix/config-partial-parse branch from 2c52723 to 3be27af Compare August 24, 2026 20:59
@asyncawaitpromise
asyncawaitpromise changed the base branch from feat/approval-retention-config to main September 1, 2026 05:11
ReadConfig handed the same default Config to the TOML parser and to its own
error paths. go-toml dereferences a non-nil pointer in place rather than
allocating, so a file which failed halfway left its partially parsed values in
the instance the error path then returned.

The caller logs "using default config" and carries on, with whatever the parser
managed to read before it failed. A malformed file could therefore turn
enforcement off, or widen admin bypass, while the logs said defaults were in
force.

Build a fresh instance per call instead: the parser gets its own, and every
error path builds another. The nil-section fixups go with it, since defaults can
no longer be clobbered and TOML cannot express a null table.

The regression test asserts the whole struct against pristine defaults rather
than sampling a couple of sections. On the unfixed code fourteen fields survive
the failed parse, including enforcement.approval and admin_bypass.enabled.

Coverage badge regenerated.
@asyncawaitpromise
asyncawaitpromise marked this pull request as ready for review September 1, 2026 07:28
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codeowners approval required for this PR:

Review catch. configDiff compares every field the struct has today, but it is a
hand-maintained list with no link to the struct, so a field added later is a field
the test silently stops covering. The open orphaned-approval PR adds one, and with
it present a malformed file could leak fetch_orphaned_approval = true through the
error path while the test still went green.

Gate on reflect.DeepEqual against a pristine default instead, and keep configDiff
only to build the failure message. A new field is then covered the moment it
exists.

Also enforce the contract callers already rely on: they log a warning and
immediately dereference the config, so every error path has to return one. That
was true but only by inspection, and is now a test.

newDefaultConfig spells out every bool rather than five of seven, since it is the
canonical statement of the defaults.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant