plandex-ai/plandex · error

error creating invite: %v

Error message

error creating invite: %v

What it means

CreateInvite inserts a row into the invites table inside an existing transaction and scans the RETURNING id back into invite.Id. When the INSERT fails (constraint violation, FK violation, bad column, connection loss), the raw driver error is wrapped with this message. The error is wrapped with %v so the underlying pq/pg error text is preserved but not typed.

Source

Thrown at app/server/db/invite_helpers.go:16

package db

import (
	"context"
	"database/sql"
	"fmt"
	"strings"

	"github.com/jmoiron/sqlx"
)

func CreateInvite(invite *Invite, tx *sqlx.Tx) error {
	err := tx.QueryRow("INSERT INTO invites (org_id, email, name, inviter_id, org_role_id) VALUES ($1, $2, $3, $4, $5) RETURNING id", invite.OrgId, invite.Email, invite.Name, invite.InviterId, invite.OrgRoleId).Scan(&invite.Id)

	if err != nil {
		return fmt.Errorf("error creating invite: %v", err)
	}

	return nil
}

func GetInvite(id string) (*Invite, error) {
	var invite Invite
	err := Conn.Get(&invite, "SELECT * FROM invites WHERE id = $1", id)

	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}

		return nil, fmt.Errorf("error getting invite: %v", err)
	}

	return &invite, nil

View on GitHub (pinned to e2d772072e)

Solutions

  1. Inspect the wrapped cause with errors.As on *pq.Error / *pgconn.PgError to distinguish constraint violation (code 23505/23503) from connection failure.
  2. Verify invite.OrgId, invite.InviterId, and invite.OrgRoleId exist in their tables before inserting.
  3. Validate Email and Name are non-empty at the handler layer to avoid NOT NULL violations.
  4. Check that the invites table schema/migrations match the five columns in the INSERT.
  5. Ensure the transaction is still valid — don't reuse a tx after a previous statement in it failed.

Example fix

// before
if err != nil {
    return fmt.Errorf("error creating invite: %v", err)
}

// after
if err != nil {
    var pgErr *pq.Error
    if errors.As(err, &pgErr) && pgErr.Code == "23503" {
        return fmt.Errorf("invite references missing org/user/role: %w", err)
    }
    return fmt.Errorf("error creating invite: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

func validateInvite(inv *db.Invite) error {
    if inv == nil || inv.OrgId == "" || inv.Email == "" || inv.InviterId == "" || inv.OrgRoleId == "" {
        return errors.New("invite missing required fields")
    }
    if _, err := mail.ParseAddress(inv.Email); err != nil {
        return fmt.Errorf("invalid email: %w", err)
    }
    return nil
}

Type guard

func isPGError(err error) (*pq.Error, bool) {
    var pgErr *pq.Error
    if errors.As(err, &pgErr) {
        return pgErr, true
    }
    return nil, false
}

Try / catch

if err := db.CreateInvite(invite, tx); err != nil {
    if pgErr, ok := isPGError(err); ok {
        switch pgErr.Code {
        case "23503":
            return fmt.Errorf("org/inviter/role not found: %w", err)
        case "23505":
            return fmt.Errorf("duplicate invite: %w", err)
        }
    }
    return fmt.Errorf("create invite: %w", err)
}

Prevention

When it happens

Trigger: tx.QueryRow("INSERT INTO invites ... RETURNING id").Scan fails: org_id or inviter_id or org_role_id references a nonexistent row, duplicate invite (unique constraint if defined), invalid NULL in a NOT NULL column (e.g. empty Email/Name), transaction already aborted by a prior error, or DB connection dropped mid-statement.

Common situations: InviteUserHandler submitting a payload with an empty email or an org_role_id belonging to another org, seeding data against a stale schema migration, or calling CreateInvite with a nil/expired transaction after an earlier statement in the same tx failed.

Related errors


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/3a2e9398416c04f2. Report an issue: GitHub.