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
- Verify invite.Id is populated — fetch the invite via GetInvite/GetActiveInviteByEmail before calling AcceptInvite instead of trusting client-supplied data.
- Validate inviteeId format (UUID) before invoking.
- Retry idempotently: an already-accepted invite returns rows-affected 0, not an error; only genuine DB failures need handling — inspect the wrapped cause.
- Check DB connectivity/pool health if errors are transient under concurrent accept load.
- 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
- Always load the invite from the DB before accepting; never trust client-supplied invite objects.
- Validate inviteeId format before the transaction.
- Make the accept flow idempotent so retries after rollback are safe.
- Retry serialization/deadlock failures with backoff under concurrent acceptance.
- Keep the transaction short — accept plus CreateOrgUser only — to reduce lock contention.
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
- error creating invite: %v
- error deleting invite: %v
- error storing default plan config: %v
- error creating plan: %v
- error inserting lockable plan id: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/423da1c62a6ecc17.
Report an issue: GitHub.