Summary
unleash.Create never checks bifrost's HTTP status. The generated client returns err only for transport failures, so any non-201 response leaves resp.JSON201 nil, and the nil flows into toUnleashInstance, which dereferences it.
Severity: low today, rising — see "Why this has not bitten yet"
Component: internal/unleash/queries.go (Create), internal/unleash/models.go:221 and the toUnleashInstance below it
Mechanism
resp, err := client.CreateInstance(ctx, req)
if err != nil {
return nil, err // transport errors only
}
unleashInstance := bifrostUnleashToK8s(resp.JSON201) // nil for any non-201
…
return toUnleashInstance(unleashInstance), nil // dereferences u.Spec
bifrostUnleashToK8s guards nil and returns nil (models.go:222-224), but toUnleashInstance starts with for _, env := range u.Spec.ExtraEnvVars with no guard.
Reachable bifrost responses today: 400 validation_failed, 400 no_version_source, 500 creation_failed.
Why this has not bitten yet
Loki, 14 days of bifrost traffic: 2985 GET /v1/releasechannels, 15 PUT /v1/unleash/{name}, and zero POST /v1/unleash. Instance creation is essentially never exercised, so the path has not been hit. No matching panic appears in nais-api's logs over the same window.
Why it is worth fixing now
nais/bifrost#549 adds two new create responses — 409 already_exists when the instance already exists, and 503 when the existence check is inconclusive. The 409 in particular is raised on a duplicate POST, which is the one non-exotic way to hit a non-201. It converts a rare data-loss path in bifrost into a nil dereference here.
Proposed fix
Check the status before converting, and map bifrost's error responses to sensible GraphQL errors — at minimum 409 should surface as "an Unleash instance already exists for this team" rather than an internal error. A nil guard in toUnleashInstance is worth having regardless, as defence in depth.
Acceptance criteria
Summary
unleash.Createnever checks bifrost's HTTP status. The generated client returnserronly for transport failures, so any non-201 response leavesresp.JSON201nil, and the nil flows intotoUnleashInstance, which dereferences it.Severity: low today, rising — see "Why this has not bitten yet"
Component:
internal/unleash/queries.go(Create),internal/unleash/models.go:221and thetoUnleashInstancebelow itMechanism
bifrostUnleashToK8sguards nil and returns nil (models.go:222-224), buttoUnleashInstancestarts withfor _, env := range u.Spec.ExtraEnvVarswith no guard.Reachable bifrost responses today:
400 validation_failed,400 no_version_source,500 creation_failed.Why this has not bitten yet
Loki, 14 days of bifrost traffic: 2985
GET /v1/releasechannels, 15PUT /v1/unleash/{name}, and zeroPOST /v1/unleash. Instance creation is essentially never exercised, so the path has not been hit. No matching panic appears in nais-api's logs over the same window.Why it is worth fixing now
nais/bifrost#549 adds two new create responses —
409 already_existswhen the instance already exists, and503when the existence check is inconclusive. The 409 in particular is raised on a duplicate POST, which is the one non-exotic way to hit a non-201. It converts a rare data-loss path in bifrost into a nil dereference here.Proposed fix
Check the status before converting, and map bifrost's error responses to sensible GraphQL errors — at minimum 409 should surface as "an Unleash instance already exists for this team" rather than an internal error. A nil guard in
toUnleashInstanceis worth having regardless, as defence in depth.Acceptance criteria
toUnleashInstancetolerates nil.