Skip to content

CREATE TENANT IF NOT EXISTS <name> silently creates tenant named 'IF' instead #119

Description

@emanzx

Tested against: origin/main @ a178aa5b6b0b260d105962c928f07304360b7b30 (fetched 2026-05-20)

Severity: HIGH (silently misnames load-bearing identity records; persists across restart)

CREATE TENANT IF NOT EXISTS <name> silently creates a tenant named 'IF', not <name>. The parser appears to consume IF NOT EXISTS as a tenant name + ignore the trailing tokens. Same parser-bug family as CREATE ROLE IF NOT EXISTS (filed separately).

Reproduction (from audit log on a fresh deployment)

The DDL run during Phase 0 bootstrap was:

CREATE TENANT IF NOT EXISTS mae8 WITH ADMIN admin_user;

The audit log records what actually happened:

seq 5: TenantCreated
  tenant_id 2
  source: nodedb
  detail: "created tenant 'IF' (id tenant:2) with admin 'IF_admin'"

Both artifacts were silently named after 'IF':

  • Tenant 2 has internal name 'IF', not 'mae8'
  • The auto-created admin user is 'IF_admin', not 'admin_user'

SHOW USERS confirms the phantom IF_admin user. The tenant naming can only be observed by reading the audit log; SHOW TENANTS shows just the tenant_id (the name field is empty due to a separate serialization bug).

Operational impact

For mae8 v2's Phase 0: tenant id=2 is internally named 'IF', not 'mae8'. Every SHOW TENANT USAGE FOR mae8 ... style query will miss this tenant by name. The workaround is to use tenant_id exclusively in SQL and never reference by name — which decouples the application from NodeDB's tenant-naming entirely.

There is no RENAME TENANT DDL to recover, and DROP TENANT 'IF' + CREATE TENANT mae8 would orphan all users on tenant_id=2 (and trip the DROP+CREATE phantom-burn bug too).

Suggested fix

  1. Add IF NOT EXISTS to the CREATE TENANT grammar (same pattern as the CREATE ROLE bug). This is a standard PostgreSQL idiom and a common DDL convention.
  2. Make CREATE USER ... TENANT '<name>' work alongside TENANT <id> (currently only the numeric ID form works), so admins don't have to look up tenant_id from SHOW TENANTS every time.
  3. Improve the audit log so an admin can grep for "expected name vs actual name" mismatches — would have caught this immediately.

Context

Caught during mae8 v2 Phase 0 bootstrap (2026-05-20). Full bug catalog: /home/system/rnd/mae8/docs/origin_bugs_2026-05-20.md (this is Bug 14 + Bug 8 from that file).

Activity

  1. farhan-syah commented on May 22, 2026

    @farhan-syah
    Member

    Fixed in 86e8ea9 (fix) + 9fa43da (TenantSelector AST) + 86a6023 (regression tests).

    Root cause

    The pgwire auth-DDL surface dispatched CREATE/DROP statements by string-prefix match and parsed them by whitespace tokenization, taking a fixed positional token as the object name. It recognized neither the standard IF [NOT] EXISTS clause nor WITH ADMIN, so CREATE TENANT IF NOT EXISTS mae8 parsed IF as the tenant name and derived IF_admin.

    Changes

    • CREATE TENANT IF NOT EXISTS <name> — clause recognized; re-creating an existing tenant is now a no-op success (no duplicate id, no audit event), not a phantom 'IF' tenant.
    • CREATE TENANT ... WITH ADMIN <user> — the auto-created admin is now named <user> instead of always <name>_admin. So CREATE TENANT IF NOT EXISTS mae8 WITH ADMIN admin_user yields tenant mae8 with admin admin_user.
    • CREATE USER ... TENANT '<name>' — tenants can now be referenced by name, not just numeric id (new TenantSelector AST type, resolved against the catalog).
    • SHOW TENANTS — now has a populated name column.
    • The fix is a shared IF [NOT] EXISTS clause-stripping helper applied uniformly across the whole auth-DDL surface, so CREATE/DROP for TENANT, ROLE, SERVICE ACCOUNT, and USER all honor the clause.

    17 integration tests added across pgwire_auth_tenants, pgwire_auth_grants, pgwire_auth_users, auth_service_account_scope, and pgwire_tenant_scoping.

    Note: the sibling issue #120 (CREATE ROLE IF NOT EXISTS) is resolved by the same fix and has its own passing coverage — it can be closed as well.

  2. added
    type:bugA defect — broken, incorrect, or lost data
    sev:1-criticalData loss, corruption, security, or crash; no workaround
    area:sqlParser, planner, SQL semantics
    on Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:sqlParser, planner, SQL semanticssev:1-criticalData loss, corruption, security, or crash; no workaroundtype:bugA defect — broken, incorrect, or lost data

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions