You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracking list of known-open defects after v0.16.0, so 0.16.1 can be scoped deliberately rather than by whoever shouts loudest.
Read this first: almost nothing here should hold up 0.16.1. One item (A1) is a genuine 0.16.0 regression with a one-line fix and is the only thing I'd argue for blocking on. A2 is a second small regression worth taking if it's cheap. Everything in the "pre-existing" tables has been broken for one or more releases, so shipping 0.16.1 without it regresses nobody.
Verified against upstream/master @ fe39828 (2026-08-11). Detailed write-ups with proofs live in POTENTIAL_1.16_ISSUES.md (item numbers below match it); the release triage view is in the remediation-plan artifact.
Every CRD below exists twice — cluster-scoped (*.sql.crossplane.io) and namespaced (*.sql.m.crossplane.io). Fixes go in one PR covering both trees, per CLAUDE.md.
A. 0.16.1 candidates (regressions introduced in 0.16.0)
A1 — Deleting a Role's connection Secret rotates the live database password · P1 · Role (postgresql)
Items 54 and 55 in POTENTIAL_1.16_ISSUES.md; standalone write-up in ISSUE_348_password_rotation.md. Introduced by crossplane-contrib#348 (be0bb83), shipped in v0.16.0.
Create never stamps Status.AtProvider.LastPasswordChange — the only stamp is in Update (pkg/controller/cluster/postgresql/role/reconciler.go:359). So for every provider-created role that field stays nil forever, and shouldResetPassword (role/utils.go:76) branches on exactly that:
54 — a GitOps prune / kubectl delete secret / namespace cleanup makes the next reconcile run ALTER ROLE ... PASSWORD ..., and consumers holding the old password stop authenticating.
55 — passwordRotationTrigger, the documented way to rotate, is never read for a normally-created role, so it silently does nothing.
Honest bounds (this is why it's P1 and not "drop everything"): only affects roles with a provider-generated password (passwordSecretRef unset) andwriteConnectionSecretToRef set; fires only when the Secret is actually missing or has an empty password key; self-limiting, because the first reset stamps the field. BYOP roles never reach the heuristic.
Fix: stamp LastPasswordChange in Create right after the successful CREATE ROLE, both trees. One line each, and it repairs 54 and 55 together. Note master already ships TestGetPassword/NilLastPasswordChangeSecretNotFound asserting changed: true for this exact input — that test encodes the bug and has to change with the fix.
Reproduce (unit, no database): a Role with WriteConnectionSecretToReference set and LastPasswordChange == nil, test.MockClient whose Get returns NotFound; assert getPassword returns changed == false. Fails on master. For item 55: same role with a healthy Secret and PasswordRotationTrigger an hour in the future; assert changed == true. Both were run in a throwaway worktree at fe39828 and failed as expected.
Related, and worth gating: PR crossplane-contrib#424 ports this design to MySQL User with the same two defects (LastPasswordChange stamped only in UpdatePassword, missing Secret ⇒ true). Ask for Create to stamp the field before merging, or it lands the same bug in a second engine.
A2 — Legacy objectType: schema resources can't be deleted after crossplane-contrib#379 · P1 · DefaultPrivileges (postgresql)
crossplane-contrib#379 made schema optional and added guards to Observe (default_privileges/reconciler.go:255,259). The second guard rejects objectType: schemawith a schema set — which is every such resource created on 0.14/0.15, because schema was +required then.
Because an Observe error returns before the deletion block in crossplane-runtime's managed reconciler (managed/reconciler.go:1118 vs :1165), those resources can't be deleted: the finalizer is never removed and kubectl delete hangs. CEL doesn't help — XValidation runs on writes and never re-validates stored objects.
Fix (~5 lines/tree): when validation fails but meta.WasDeleted(mg) is true, return ResourceExists: false instead of an error. Create always failed for these, so there's no external state to revoke. Needs a regression test with a deletion timestamp in both trees.
Alternative if 0.16.1 needs to be small: ship a release note telling upgraders to remove spec.schema from objectType: schema resources, and fix it in 0.17.
B. Also new in 0.16.0, but not release-blocking
Item
CRD
Pri
Summary
52
User (mysql)
P2
authenticationPlugin.authString: "" never converges — Observe maps '' to nil, authPluginEqual (mysql/user/reconciler.go:296) reads that as drift, ALTER USER ... IDENTIFIED WITH re-fires every reconcile. Needs a deliberately empty string. ~2 lines: normalise ""↔nil, or add MinLength=1.
56
User (mysql)
P1*
Adding authenticationPlugin: {name: caching_sha2_password} to a User that already has a password makes Update emit ALTER USER ... IDENTIFIED WITH <plugin> with no AS clause (:494-506). *The provider half is proven; the engine half is not — nobody has confirmed MySQL actually clears the credential in that case. Run it against mysql:8 before treating it as P1; if the credential survives, this folds into item 52.
57
User (mysql)
P3
user_types.go:82 tells you to use passwordSecretRef with a native password plugin; the CEL rule at :38 makes those two mutually exclusive. Doc-only.
58
User (mysql)
P3
Not a new defect — crossplane-contrib#363 added call sites of the QuoteValue helper from item 12 (below), including the user-controlled authString. Just means item 12's fix has more callers than it used to.
C. Pre-existing — none of this regresses 0.16.x
Broken for at least one release. High user pain in places, but shipping 0.16.1 without them changes nothing for anyone.
PostgreSQL
Item
CRD
Pri
Summary
3, 46, 51
DefaultPrivileges
P1
Observe filters on neither defaclnamespace nor defaclrole, so resources differing only by schema or target role alias onto each other and never converge; ALL is never expanded on the desired side; Create's REVOKE ALL erases sibling CRs' privileges. Broken since 0.14. One query rewrite; 51 needs a semantics decision first.
16, 17, 18
Grant
P2
Observe demands the ACL equal the spec exactly, while Create only revokes the spec's own privileges. Consequences: two Grants on one object fight forever (18); a grant to the object's owner never converges (17); a drifted Grant reports ResourceExists: false, so deleting it skips the REVOKE and leaves the privilege behind (16). crossplane-contrib#421 fixed the database-level case; tables/sequences/schemas still exact-match — see selectTableGrantQuery's array_agg(...) = .... Containment vs. equality is a design decision — state it in the PR.
4, 6, 48, 50
Role
P1
upToDate (role/reconciler.go:437) compares pointers, not values, so every role reports out-of-date from reconcile #2 and Update re-issues ALTER ROLE ... CONNECTION LIMIT forever (issue crossplane-contrib#194). Also: Create and Update quote configurationParameters differently and Observe can't parse what PostgreSQL stores back (6); out-of-band rolconfig drift is detected but never written (48); Create omits CONNECTION LIMIT (50).
41, 42
Database
P2
isTemplate: true makes the CR undeletable — Delete issues a bare DROP DATABASE (database/reconciler.go:311) and PostgreSQL refuses. upToDate also diffs four axes Update can't write, so non-canonical encoding spellings loop forever; owner: DEFAULT, which the field's own doc recommends, is a hard create failure.
43
Extension
P1
spec.forProvider.schema is a no-op end to end — not emitted by Create, not read by Observe, Update is empty. Version drift is diffed but never written. Decide: implement it or remove it (removing later is breaking).
44
Schema
P2
revokePublicOnSchema is write-only — never observed, so out-of-band re-grants to PUBLIC are invisible drift on a security knob. Same class as the already-fixed item 2; commit 93b607b's acl.grantee = 0 clause is the template.
31, 45
clients / all PG CRDs
P1
DSN() (pkg/clients/postgresql/postgresql.go:50) embeds the password in a URL; when it fails to parse, lib/pq echoes the whole DSN — password included — into status.conditions (issue crossplane-contrib#266). Same function: database is concatenated without url.PathEscape, so a crafted name can override sslmode/host. Pre-existing, so not a 0.16.1 blocker, but it's cheap and it's a credential leak.
MySQL
Item
CRD
Pri
Summary
30, 7, 8
Grant
P1
MySQL collapses a complete privilege list to ALL PRIVILEGES; diffPermissions compares that against the user's explicit list and issues REVOKE ALL + re-GRANT every reconcile — a real window with no privileges (issue crossplane-contrib#162). Also: REVOKE ... WITH GRANT OPTION (mysql/grant/reconciler.go:364,380) is invalid MySQL, so deleting such a grant wedges the finalizer. The e2e suite runs MariaDB, which accepts that syntax — that's why it never caught it.
5
User
P1
Same pointer comparison as item 4 (mysql/user/reconciler.go:566): any User with resourceOptions never reports up-to-date.
9
Database
P1
defaultCollation is late-initialised from the server into the spec; a later charset-only change then emits an incompatible COLLATE and Update fails forever.
MSSQL
Item
CRD
Pri
Summary
19, 20, 21
User
P1
Contained-user support shipped in 0.15 with zero tests. The immutability CEL rule has an unset→set hole, so contained: true can be patched onto an existing login-mapped user; Delete then drops the user but not the login, leaving a working server credential behind. The namespaced tree emits USE [db];, which Azure SQL rejects, and database isn't required, so the cluster tree silently creates the user in master.
11, 12
Grant, clients
P1/P3
mssql.QuoteIdentifier (pkg/clients/mssql/mssql.go:130) doesn't escape ], and the grant path interpolates the schema name raw (mssql/grant/reconciler.go:258). Mostly a correctness bug: a schema named My-Schema simply doesn't work.
23
Grant
P2
Update revokes every database-class permission not in the spec, so two Grants on one user fight, and any Grant omitting CONNECT locks the user out. Pre-existing since 2021 — document the one-Grant-per-user constraint, or adopt whatever containment semantics items 16–18 land on.
Cross-cutting
Item
Affects
Pri
Summary
35
every CRD
P2
Connection details are returned from Update only when the password changed, so a ProviderConfig endpoint change never reaches the managed resource's Secret (crossplane-contrib#242) and adopted roles never publish a username (crossplane-contrib#77). Touches every reconciler — schedule it alone, it conflicts with everything.
22
ClusterProviderConfig (postgresql, mssql)
P2
Only the MySQL namespaced config package registers a reconciler for its ClusterProviderConfig, so the PG and MSSQL ones get no finalizer and no status.users — delete one while in use and it goes immediately, breaking every dependent MR.
25
provider startup
P3
Every Setup registers a state-metrics recorder whose Start returns the List error, which kingpin.FatalIfError turns into process exit — a CRD not established ~5s after start crash-loops the provider instead of leaving a metric unpopulated. ~5 lines: log instead of return, plus a nil guard on MetricOptions.
27
namespaced CRDs
P3
Marker defects from the tree split: namespaced MySQL Grant doesn't require forProvider, namespaced MSSQL User lost its categories, namespaced MySQL ProviderConfig TLS refs are cross-namespace, and 4 of 6 ProviderConfig CRDs have a stale SECRET-NAME printcolumn. Includes a real nil-deref: Observe panics on a Grant with no user in both trees.
49
all PG CRDs
P2
No adoption or ownership policy: two CRs pointing at one external object produce owner ping-pong (Database, Schema), silently stale credentials (Role), or destructive CASCADE deletes. Needs one documented answer, not six per-resource behaviours.
Working on any of these
Both trees, one PR.pkg/controller/cluster/... and pkg/controller/namespaced/..., plus apis/cluster and apis/namespaced. Diff the two diffs against each other before review.
Detail is in POTENTIAL_1.16_ISSUES.md — each item there has the proof, the exact SQL or Go, the ruled-out hypotheses, and a fix direction. Read the item before starting; several have a "this looked like a bug and isn't" note that will save a round.
Probes get promoted, not deleted. Any throwaway test that demonstrates one of these should land as a regression test with the fix.
Unit tests use go-sqlmock / table-driven go-cmp; make reviewable before pushing; e2e needs Docker and only one run at a time (the KIND cluster name is shared).
Already fixed — don't re-do these
#1 routine-grant argument quoting, #15 grants on views/partitioned tables, #47a Connect on backends without server_version_num, #47b database grants no longer needing a session on the target DB, #2revokePublicOnDb, #17 for databases (tables/sequences/schemas still open, see above) — all in crossplane-contrib#421. #32objectType: schema SQL in crossplane-contrib#379. #34 shared login in crossplane-contrib#411. #36 x/net CVE in crossplane-contrib#407. #14/#37 CodeQL version mismatch via the action bump.
Tracking list of known-open defects after v0.16.0, so 0.16.1 can be scoped deliberately rather than by whoever shouts loudest.
Read this first: almost nothing here should hold up 0.16.1. One item (A1) is a genuine 0.16.0 regression with a one-line fix and is the only thing I'd argue for blocking on. A2 is a second small regression worth taking if it's cheap. Everything in the "pre-existing" tables has been broken for one or more releases, so shipping 0.16.1 without it regresses nobody.
Verified against
upstream/master@fe39828(2026-08-11). Detailed write-ups with proofs live inPOTENTIAL_1.16_ISSUES.md(item numbers below match it); the release triage view is in the remediation-plan artifact.Every CRD below exists twice — cluster-scoped (
*.sql.crossplane.io) and namespaced (*.sql.m.crossplane.io). Fixes go in one PR covering both trees, perCLAUDE.md.A. 0.16.1 candidates (regressions introduced in 0.16.0)
A1 — Deleting a Role's connection Secret rotates the live database password · P1 ·
Role(postgresql)Items 54 and 55 in
POTENTIAL_1.16_ISSUES.md; standalone write-up inISSUE_348_password_rotation.md. Introduced by crossplane-contrib#348 (be0bb83), shipped in v0.16.0.Createnever stampsStatus.AtProvider.LastPasswordChange— the only stamp is inUpdate(pkg/controller/cluster/postgresql/role/reconciler.go:359). So for every provider-created role that field staysnilforever, andshouldResetPassword(role/utils.go:76) branches on exactly that:Two symptoms from one asymmetry:
kubectl delete secret/ namespace cleanup makes the next reconcile runALTER ROLE ... PASSWORD ..., and consumers holding the old password stop authenticating.passwordRotationTrigger, the documented way to rotate, is never read for a normally-created role, so it silently does nothing.Honest bounds (this is why it's P1 and not "drop everything"): only affects roles with a provider-generated password (
passwordSecretRefunset) andwriteConnectionSecretToRefset; fires only when the Secret is actually missing or has an empty password key; self-limiting, because the first reset stamps the field. BYOP roles never reach the heuristic.Fix: stamp
LastPasswordChangeinCreateright after the successfulCREATE ROLE, both trees. One line each, and it repairs 54 and 55 together. Notemasteralready shipsTestGetPassword/NilLastPasswordChangeSecretNotFoundassertingchanged: truefor this exact input — that test encodes the bug and has to change with the fix.Reproduce (unit, no database): a
RolewithWriteConnectionSecretToReferenceset andLastPasswordChange == nil,test.MockClientwhoseGetreturns NotFound; assertgetPasswordreturnschanged == false. Fails on master. For item 55: same role with a healthy Secret andPasswordRotationTriggeran hour in the future; assertchanged == true. Both were run in a throwaway worktree atfe39828and failed as expected.A2 — Legacy
objectType: schemaresources can't be deleted after crossplane-contrib#379 · P1 ·DefaultPrivileges(postgresql)Item 53. Introduced by crossplane-contrib#379, shipped in v0.16.0.
crossplane-contrib#379 made
schemaoptional and added guards toObserve(default_privileges/reconciler.go:255,259). The second guard rejectsobjectType: schemawith a schema set — which is every such resource created on 0.14/0.15, becauseschemawas+requiredthen.Because an
Observeerror returns before the deletion block in crossplane-runtime's managed reconciler (managed/reconciler.go:1118vs:1165), those resources can't be deleted: the finalizer is never removed andkubectl deletehangs. CEL doesn't help —XValidationruns on writes and never re-validates stored objects.Fix (~5 lines/tree): when validation fails but
meta.WasDeleted(mg)is true, returnResourceExists: falseinstead of an error.Createalways failed for these, so there's no external state to revoke. Needs a regression test with a deletion timestamp in both trees.Alternative if 0.16.1 needs to be small: ship a release note telling upgraders to remove
spec.schemafromobjectType: schemaresources, and fix it in 0.17.B. Also new in 0.16.0, but not release-blocking
User(mysql)authenticationPlugin.authString: ""never converges —Observemaps''tonil,authPluginEqual(mysql/user/reconciler.go:296) reads that as drift,ALTER USER ... IDENTIFIED WITHre-fires every reconcile. Needs a deliberately empty string. ~2 lines: normalise""↔nil, or addMinLength=1.User(mysql)authenticationPlugin: {name: caching_sha2_password}to aUserthat already has a password makesUpdateemitALTER USER ... IDENTIFIED WITH <plugin>with noASclause (:494-506). *The provider half is proven; the engine half is not — nobody has confirmed MySQL actually clears the credential in that case. Run it againstmysql:8before treating it as P1; if the credential survives, this folds into item 52.User(mysql)user_types.go:82tells you to usepasswordSecretRefwith a native password plugin; the CEL rule at:38makes those two mutually exclusive. Doc-only.User(mysql)QuoteValuehelper from item 12 (below), including the user-controlledauthString. Just means item 12's fix has more callers than it used to.C. Pre-existing — none of this regresses 0.16.x
Broken for at least one release. High user pain in places, but shipping 0.16.1 without them changes nothing for anyone.
PostgreSQL
DefaultPrivilegesObservefilters on neitherdefaclnamespacenordefaclrole, so resources differing only by schema or target role alias onto each other and never converge;ALLis never expanded on the desired side;Create'sREVOKE ALLerases sibling CRs' privileges. Broken since 0.14. One query rewrite; 51 needs a semantics decision first.GrantObservedemands the ACL equal the spec exactly, whileCreateonly revokes the spec's own privileges. Consequences: two Grants on one object fight forever (18); a grant to the object's owner never converges (17); a drifted Grant reportsResourceExists: false, so deleting it skips the REVOKE and leaves the privilege behind (16). crossplane-contrib#421 fixed the database-level case; tables/sequences/schemas still exact-match — seeselectTableGrantQuery'sarray_agg(...) = .... Containment vs. equality is a design decision — state it in the PR.RoleupToDate(role/reconciler.go:437) compares pointers, not values, so every role reports out-of-date from reconcile #2 andUpdatere-issuesALTER ROLE ... CONNECTION LIMITforever (issue crossplane-contrib#194). Also:CreateandUpdatequoteconfigurationParametersdifferently andObservecan't parse what PostgreSQL stores back (6); out-of-bandrolconfigdrift is detected but never written (48);CreateomitsCONNECTION LIMIT(50).DatabaseisTemplate: truemakes the CR undeletable —Deleteissues a bareDROP DATABASE(database/reconciler.go:311) and PostgreSQL refuses.upToDatealso diffs four axesUpdatecan't write, so non-canonicalencodingspellings loop forever;owner: DEFAULT, which the field's own doc recommends, is a hard create failure.Extensionspec.forProvider.schemais a no-op end to end — not emitted byCreate, not read byObserve,Updateis empty. Version drift is diffed but never written. Decide: implement it or remove it (removing later is breaking).SchemarevokePublicOnSchemais write-only — never observed, so out-of-band re-grants to PUBLIC are invisible drift on a security knob. Same class as the already-fixed item 2; commit93b607b'sacl.grantee = 0clause is the template.DSN()(pkg/clients/postgresql/postgresql.go:50) embeds the password in a URL; when it fails to parse, lib/pq echoes the whole DSN — password included — intostatus.conditions(issue crossplane-contrib#266). Same function:databaseis concatenated withouturl.PathEscape, so a crafted name can overridesslmode/host. Pre-existing, so not a 0.16.1 blocker, but it's cheap and it's a credential leak.MySQL
GrantALL PRIVILEGES;diffPermissionscompares that against the user's explicit list and issuesREVOKE ALL+ re-GRANTevery reconcile — a real window with no privileges (issue crossplane-contrib#162). Also:REVOKE ... WITH GRANT OPTION(mysql/grant/reconciler.go:364,380) is invalid MySQL, so deleting such a grant wedges the finalizer. The e2e suite runs MariaDB, which accepts that syntax — that's why it never caught it.Usermysql/user/reconciler.go:566): any User withresourceOptionsnever reports up-to-date.DatabasedefaultCollationis late-initialised from the server into the spec; a later charset-only change then emits an incompatibleCOLLATEandUpdatefails forever.MSSQL
Usercontained: truecan be patched onto an existing login-mapped user;Deletethen drops the user but not the login, leaving a working server credential behind. The namespaced tree emitsUSE [db];, which Azure SQL rejects, anddatabaseisn't required, so the cluster tree silently creates the user inmaster.Grant, clientsmssql.QuoteIdentifier(pkg/clients/mssql/mssql.go:130) doesn't escape], and the grant path interpolates the schema name raw (mssql/grant/reconciler.go:258). Mostly a correctness bug: a schema namedMy-Schemasimply doesn't work.GrantUpdaterevokes every database-class permission not in the spec, so two Grants on one user fight, and any Grant omittingCONNECTlocks the user out. Pre-existing since 2021 — document the one-Grant-per-user constraint, or adopt whatever containment semantics items 16–18 land on.Cross-cutting
Updateonly when the password changed, so a ProviderConfig endpoint change never reaches the managed resource's Secret (crossplane-contrib#242) and adopted roles never publish a username (crossplane-contrib#77). Touches every reconciler — schedule it alone, it conflicts with everything.ClusterProviderConfig(postgresql, mssql)ClusterProviderConfig, so the PG and MSSQL ones get no finalizer and nostatus.users— delete one while in use and it goes immediately, breaking every dependent MR.Setupregisters a state-metrics recorder whoseStartreturns theListerror, whichkingpin.FatalIfErrorturns into process exit — a CRD not established ~5s after start crash-loops the provider instead of leaving a metric unpopulated. ~5 lines: log instead of return, plus a nil guard onMetricOptions.Grantdoesn't requireforProvider, namespaced MSSQLUserlost its categories, namespaced MySQLProviderConfigTLS refs are cross-namespace, and 4 of 6 ProviderConfig CRDs have a staleSECRET-NAMEprintcolumn. Includes a real nil-deref:Observepanics on a Grant with nouserin both trees.Database,Schema), silently stale credentials (Role), or destructiveCASCADEdeletes. Needs one documented answer, not six per-resource behaviours.Working on any of these
pkg/controller/cluster/...andpkg/controller/namespaced/..., plusapis/clusterandapis/namespaced. Diff the two diffs against each other before review.POTENTIAL_1.16_ISSUES.md— each item there has the proof, the exact SQL or Go, the ruled-out hypotheses, and a fix direction. Read the item before starting; several have a "this looked like a bug and isn't" note that will save a round.Observemust issue zero writes on reconcile chore: bump korthout/backport-action from 1.4.0 to 3.2.0 #2. Most items above are that invariant failing.go-sqlmock/ table-drivengo-cmp;make reviewablebefore pushing; e2e needs Docker and only one run at a time (the KIND cluster name is shared).Already fixed — don't re-do these
#1 routine-grant argument quoting, #15 grants on views/partitioned tables, #47a Connect on backends without
server_version_num, #47b database grants no longer needing a session on the target DB, #2revokePublicOnDb, #17 for databases (tables/sequences/schemas still open, see above) — all in crossplane-contrib#421. #32objectType: schemaSQL in crossplane-contrib#379. #34 shared login in crossplane-contrib#411. #36 x/net CVE in crossplane-contrib#407. #14/#37 CodeQL version mismatch via the action bump.