crossplane-contrib / crossplane-contrib/provider-upjet-github

RepositoryRuleset Create/Update panics on non-404/304 GetRuleset errors (nil pointer deref, bundled terraform-provider-github v6.6.0)

Open Beginner friendly
#294 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
56
Forks
46
Avg merge
6d 7h
Merged PRs (30d)
2

Description

### What happened

`RepositoryRuleset` (`repo.github.upbound.io/v1alpha1`) resources intermittently get stuck `Synced=False`/`Ready=False` indefinitely with no visible error on the managed resource. The provider pod panics on `Create`/`Update`:

```
E0716 22:53:08.603330 1 runtime.go:142] "Observed a panic" panic="runtime error: invalid memory address or nil pointer dereference" panicGoValue="\"invalid memory address or nil pointer dereference\"" stacktrace=<
goroutine 190735 [running]:
k8s.io/apimachinery/pkg/util/runtime.logPanic({0x2d1a398, 0xc0002f3180}, {0x24bab40, 0x435a090})
k8s.io/apimachinery@v0.34.3/pkg/util/runtime/runtime.go:132 +0xbc
github.com/crossplane/upjet/v2/pkg/controller.(*panicHandler).recoverIfPanic(0xc0057b3f28, {0x2d1a398, 0xc0002f3180})
github.com/crossplane/upjet/v2@v2.2.0/pkg/controller/external_async_tfpluginfw.go:199 +0x82
panic({0x24bab40?, 0x435a090?})
runtime/panic.go:792 +0x132
github.com/integrations/terraform-provider-github/v6/github.resourceGithubRepositoryRulesetRead(0xc00e436750, {0x232d880?, 0xc00659ecc0?})
github.com/integrations/terraform-provider-github/v6@v6.6.0/github/resource_github_repository_ruleset.go:580 +0x567
github.com/integrations/terraform-provider-github/v6/github.resourceGithubRepositoryRulesetCreate(0xc00e436750, {0x232d880, 0xc00659ecc0})
github.com/integrations/terraform-provider-github/v6@v6.6.0/github/resource_github_repository_ruleset.go:542 +0x114
github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema.(*Resource).create(0x2d1a398?, {0x2d1a398?, 0xc0002f3180?}, 0xd?, {0x232d880?, 0xc00659ecc0?})
github.com/hashicorp/terraform-plugin-sdk/v2@v2.37.0/helper/schema/resource.go:837 +0x15f
github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema.(*Resource).Apply(0xc000664600, {0x2d1a398, 0xc0002f3180}, 0x0, 0xc00d1a5d40, {0x232d880, 0xc00659ecc0})
github.com/hashicorp/terraform-plugin-sdk/v2@v2.37.0/helper/schema/resource.go:980 +0xb47
github.com/crossplane/upjet/v2/pkg/controller.(*terraformPluginSDKExternal).Create(0xc00daba4b0, {0x2d1a398, 0xc0002f3180}, {0x2d46488, 0xc00db64008})
github.com/crossplane/upjet/v2@v2.2.0/pkg/controller/external_tfpluginsdk.go:610 +0xdc
github.com/crossplane/upjet/v2/pkg/controller.(*terraformPluginSDKAsyncExternal).Create.func1()
github.com/crossplane/upjet/v2@v2.2.0/pkg/controller/external_async_tfpluginsdk.go:171 +0x1dd
created by github.com/crossplane/upjet/v2/pkg/controller.(*terraformPluginSDKAsyncExternal).Create in goroutine 3894
github.com/crossplane/upjet/v2@v2.2.0/pkg/controller/external_async_tfpluginsdk.go:146 +0x13f
>
```

We also see the same panic via `resourceGithubRepositoryRulesetUpdate` (`resource_github_repository_ruleset.go:613` calling into `:580`) — same root cause, different caller.

Because `upjet`'s async Create/Update runs under a panic-recovery goroutine wrapper, the panic is swallowed silently at that layer: Crossplane never receives a completion callback, so the managed resource just sits `Ready=False`/`Synced=False` forever with no error surfaced on its conditions or events. This makes the failure mode especially hard to diagnose from the Crossplane side — the only signal is the provider pod's raw stdout panic log.

### Root cause

In the bundled `terraform-provider-github v6.6.0`, `resourceGithubRepositoryRulesetRead` only explicitly handles a `*github.ErrorResponse` with status `304` or `404` (both `return nil` early):

```go
ruleset, resp, err = client.Repositories.GetRuleset(ctx, owner, repoName, rulesetID, false)
if err != nil {
if ghErr, ok := err.(*github.ErrorResponse); ok {
if ghErr.Response.StatusCode == http.StatusNotModified {
return nil
}
if ghErr.Response.StatusCode == http.StatusNotFound {
...
return nil
}
}
}

d.Set("etag", resp.Header.Get("ETag")) // <-- resp/ruleset are nil for any other error
d.Set("name", ruleset.Name)
```

For **any other error** — a `*github.RateLimitError`/`*github.AbuseRateLimitError` (GitHub secondary rate limiting), a 5xx, a transient network error, or any other `*github.ErrorResponse` status — execution falls through the `if err != nil { ... }` block with no `else return err`, and then unconditionally dereferences the now-nil `resp`/`ruleset`. Since `resourceGithubRepositoryRulesetRead` is called from both `resourceGithubRepositoryRulesetCreate` and `...Update`, any transient GitHub API error during either operation panics.

**This is already fixed upstream in `integrations/terraform-provider-github`** — verified by diffing `resourceGithubRepositoryRulesetRead` across tags directly:

- `v6.6.0` / `v6.7.0` / `v6.8.0`: no `else return err`. (`v6.7.0`+ does add a `ruleset == nil` guard right after the error block, which incidentally prevents the crash, though it incorrectly treats *any* unhandled error as "ruleset deleted" rather than surfacing it.)
- `v6.9.0`: adds the correct `return err` fallback for the general-error case.
- `v6.10.0`+: same fix, migrated to `return diag.FromErr(err)`.

### Confirmed impact

Real production impact, not just theoretical: Datadog logs (`service:provider-upjet-github`) show **0 occurrences of this panic in the ~88 days prior to 2026-07-15**, then **79 panics across 2026-07-15/16**, nearly all on our production control plane, correlated with a legitimate composition change on our side that triggered a simultaneous `Update()` across many `RepositoryRuleset` resources at once (plausibly tripping GitHub's secondary rate limiting at that volume, which then hit this unhandled-error panic instead of a clean retry).

### Relationship to #289 / #288

This is a **different** code path from #289 (`OrganizationRuleset`'s `required_status_checks` interface-conversion panic in `respository_rules_utils.go`) — this one is in `RepositoryRuleset`'s `Read`, triggered by transient/rate-limit errors rather than a missing schema field. But both share the same underlying cause: the bundled `terraform-provider-github` dependency is pinned to `v6.6.0`, several versions behind where each bug was fixed. **#288 (bumping to `v6.12.1`) would resolve this issue too**, as a side effect of picking up the `v6.9.0`+ fix.

Given there are now two independent, production-impacting panics that both trace back to the stale `v6.6.0` pin, it'd be great to get #288 reviewed and merged.

### How to reproduce

Not deterministically reproducible on demand — it requires `GetRuleset` to return a non-404/304 error during a `RepositoryRuleset` Create or Update, which in practice means hitting it during a transient GitHub API failure or secondary rate limiting (e.g. during a burst of concurrent ruleset operations across many repos).

### Environment

- provider-upjet-github: v0.19.0 / v0.19.1 (latest) — go.mod still pins `terraform-provider-github/v6 v6.6.0`
- upjet: v2.2.0

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with go.mod and issue #288 to confirm how the terraform-provider-github dependency is being bumped from v6.6.0. Use the upstream RepositoryRuleset Read fix as the version criterion, then verify the provider builds and that non-404/304 GetRuleset errors are returned instead of causing a nil-pointer panic.

Written by the indexing model from the issue text.

Assessment

Tech stack
github, go
Domain
api, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.