Skip to content

fix: preserve repository fields a read-only token cannot see - #1

Open
th7 wants to merge 1 commit into
upstream-mainfrom
fix/preserve-unreadable-repo-fields
Open

fix: preserve repository fields a read-only token cannot see#1
th7 wants to merge 1 commit into
upstream-mainfrom
fix/preserve-unreadable-repo-fields

Conversation

@th7

@th7 th7 commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Refs integrations#3382

Note: this PR was largely written with AI assistance, per CONTRIBUTING.md.
The reproduction and the nine-repository verification below are my own, against
a real under-privileged token.


Before the change?

GitHub omits merge-related settings from the repository object unless the caller
has push or admin access — per the REST docs, "To view merge-related settings,
you must have the contents:read and contents:write permissions."

The read path could not tell "omitted" from "false", since a Get* accessor
returns the zero value for both. An under-privileged token therefore wrote
false/"" into state for fields it had never seen, producing a diff on every
plan that no apply could clear — the value an apply would write back is not
readable either. Any GitHub App token scoped to administration:read +
contents:read has a permanently dirty plan.

After the change?

Only fields present in the response are written, so an omitted field keeps its
prior state.

Preserving state rather than raising an error is what the provider already
decided. integrations#3144 established that an absent field means "the caller cannot see
it"
, and that the provider should detect that itself rather than make the
practitioner declare it — it keyed the vulnerability-alerts read off the
presence of security_and_analysis, and retired the manual opt-out
ignore_vulnerability_alerts_during_read as "ignored as the provider now
handles lack of permissions automatically."
integrations#3553 continued in that direction,
replacing hard errors on archived/deleted repos with a warning and a state
removal. The merge-related fields have the identical failure mode and were
simply never covered.

The trade-off is the one integrations#3144 already accepted: drift goes undetected while the
token cannot read these fields. That beats a diff no apply can resolve.

Relationship to integrations#3556. That PR fixes a different bug on four of the same
fields — GitHub rejects changes to merge_commit_* while the strategy is
disabled (422 no_merge_strategy) — via DiffSuppressFunc in the schema. This
one is about fields missing from the response. Complementary, and they merge
cleanly: integrations#3556 touches the schema block, this touches the read path.

Scope — the archived guard is untouched, byte-identical including its TODO.
Folding archiving into the same presence check was tempting, since that TODO
doubts the fields are omitted for archived repos at all, but I have no archived
repository to verify against and would rather not ship a second, untested
behaviour change alongside this one. Happy to follow up if a maintainer can
confirm.

Verification. Reproduced with a scoped installation token holding
administration:read and contents:read across nine repositories: every one
reported the diff on every plan before, none do after. Reverting to the released
provider brings it back, so the binary is the only variable.

Pull request checklist

  • Schema migrations have been created if needed — not needed, no schema change
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been reviewed and added / updated if needed (for bug fixes / features)

TestResourceGithubRepositoryRead_omittedFields drives the real read against an
httptest server, with the field omitted and with it present. It fails if the
fix is reverted, so it pins the regression rather than just exercising the
helper. No acceptance test: reproducing this needs a deliberately
under-privileged token, which the acceptance suite cannot provision.

Does this introduce a breaking change?

  • Yes
  • No

A token that can read these fields sees no change. A token that cannot stops
seeing an unresolvable diff and keeps its last known values instead.

GitHub omits merge-related settings from the repository object unless the
caller has push or admin access: "To view merge-related settings, you must
have the contents:read and contents:write permissions."

Read through a Get* accessor, an omitted field yields the zero value, which
is indistinguishable from the field genuinely being false or empty. Writing
that to state produces a diff on every plan that no apply can resolve,
because the value an apply would write back is not readable either. Setting
only what the response actually contained leaves prior state in place
instead.

integrations#3144 took this approach for vulnerability_alerts and security_and_analysis,
keying off the presence of security_and_analysis in the response and
deprecating ignore_vulnerability_alerts_during_read as "ignored as the
provider now handles lack of permissions automatically". setIfPresent
extends the same treatment to the merge-related fields, and sits in util.go
next to resourceKeysGetOk because the bug class is not repository-specific.

Skipping a field silently would hide the trade-off, so the read logs one
tflog.Warn per repository naming the fields it could not read. integrations#3553 uses
the same signal for archived and deleted repositories. A user-facing
warning diagnostic would fire on every plan for a token that is
permanently read-only, which is noise rather than information.

The archived special-case is deliberately left in place. It may be redundant
— the TODO added alongside integrations#3144 doubts that GitHub omits these fields for
archived repositories at all — but that is unverified, and removing it would
be a second, untested behaviour change on top of this one.

The cost is that drift in these fields goes undetected while the token
cannot read them, which is preferable to a permanent diff no apply can
clear.

Verified with a scoped installation token holding administration:read and
contents:read against a configuration managing nine repositories: every one
reported the diff on every plan before, none do after. Reverting to the
released provider reproduces it, so the binary is the only variable.

Refs integrations#3382
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant