plandex-ai/plandex · error

error accepting invite: %v

Error message

error accepting invite: %v

What it means

AcceptInvite runs inside a WithTx ('accept invite') transaction: it stamps accepted_at/invitee_id on the invite, then creates the org user. The wrapped 'error accepting invite' message is returned by the inner UPDATE when it fails, causing WithTx to roll back the whole acceptance. The invite is not marked accepted and no org membership is created.

Source

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

	if tx == nil {
		_, err = Conn.Exec(query, id)
	} else {
		_, err = tx.Exec(query, id)
	}

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

	return nil
}

func AcceptInvite(ctx context.Context, invite *Invite, inviteeId string) error {
	err := WithTx(ctx, "accept invite", func(tx *sqlx.Tx) error {

		_, err := tx.Exec(`UPDATE invites SET accepted_at = NOW(), invitee_id = $1 WHERE id = $2`, inviteeId, invite.Id)
		if err != nil {
			return fmt.Errorf("error accepting invite: %v", err)
		}

		// create org user
		err = CreateOrgUser(invite.OrgId, inviteeId, invite.OrgRoleId, tx)

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

		return nil
	})

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

	invite.InviteeId = &inviteeId

View on GitHub (pinned to e2d772072e)

Solutions

  1. Verify invite.Id is populated — fetch the invite via GetInvite/GetActiveInviteByEmail before calling AcceptInvite instead of trusting client-supplied data.
  2. Validate inviteeId format (UUID) before invoking.
  3. Retry idempotently: an already-accepted invite returns rows-affected 0, not an error; only genuine DB failures need handling — inspect the wrapped cause.
  4. Check DB connectivity/pool health if errors are transient under concurrent accept load.
  5. Apply migrations if the error mentions unknown columns; WithTx will have rolled back, so simply re-run after fixing.

Example fix

// before: accepting a client-supplied invite object directly
var invite db.Invite
json.NewDecoder(r.Body).Decode(&invite)
err := db.AcceptInvite(ctx, &invite, userId)

// after: load the invite from the DB first
invite, err := db.GetInvite(inviteId)
if err != nil || invite == nil {
    http.Error(w, "invite not found", http.StatusNotFound)
    return
}
err = db.AcceptInvite(ctx, invite, userId)
Defensive patterns

Strategy: validation

Validate before calling

invite, err := db.GetInvite(inviteId)
if err != nil {
    return fmt.Errorf("load invite: %w", err)
}
if invite == nil {
    return errors.New("invite not found")
}
if _, err := uuid.Parse(inviteeId); err != nil {
    return fmt.Errorf("invalid invitee id %q", inviteeId)
}

Type guard

func isAcceptableInvite(invite *db.Invite) bool {
    return invite != nil && invite.Id != "" && invite.OrgId != "" && invite.OrgRoleId != ""
}

Try / catch

if err := db.AcceptInvite(ctx, invite, inviteeId); err != nil {
    var pgErr *pq.Error
    if errors.As(err, &pgErr) && (pgErr.Code == "40001" || pgErr.Code == "40P01") {
        // serialization failure / deadlock: safe to retry — WithTx rolled back
        return retryAccept(ctx, invite, inviteeId)
    }
    return fmt.Errorf("accept invite: %w", err)
}

Prevention

When it happens

Trigger: tx.Exec("UPDATE invites SET accepted_at = NOW(), invitee_id = $1 WHERE id = $2") fails: invite.Id is empty/invalid (stale invite object, e.g. already-accepted row is fine for the UPDATE but empty Id breaks the WHERE binding), malformed inviteeId vs. column type, DB connection drop mid-transaction, or schema mismatch on invites.

Common situations: Race where two acceptance requests run concurrently and one hits serialization/lock timeouts, invite object constructed from client payload without fetching the row (so Id is blank), or DB failover during the transaction.

Related errors


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