plandex-ai/plandex · error
error updating user num_non_draft_plans: %v
Error message
error updating user num_non_draft_plans: %v
What it means
This error wraps a failure to increment the num_non_draft_plans counter for a user inside a transaction. The UPDATE statement on the users table failed, so the transaction caller receives a wrapped sqlx/driver error. It indicates the counter could not be kept in sync when a plan transitions out of draft status.
Source
Thrown at app/server/db/plan_helpers.go:303
return nil
}
func IncActiveBranches(planId string, inc int, tx *sqlx.Tx) error {
_, err := tx.Exec("UPDATE plans SET active_branches = active_branches + $1 WHERE id = $2", inc, planId)
if err != nil {
return fmt.Errorf("error updating plan active branches: %v", err)
}
return nil
}
func IncNumNonDraftPlans(userId string, tx *sqlx.Tx) error {
_, err := tx.Exec("UPDATE users SET num_non_draft_plans = num_non_draft_plans + 1 WHERE id = $1", userId)
if err != nil {
return fmt.Errorf("error updating user num_non_draft_plans: %v", err)
}
return nil
}
func StoreDescription(description *ConvoMessageDescription) error {
descriptionsDir := getPlanDescriptionsDir(description.OrgId, description.PlanId)
err := os.MkdirAll(descriptionsDir, os.ModePerm)
if err != nil {
return fmt.Errorf("error creating convo message descriptions dir: %v", err)
}
for _, op := range description.Operations {
if op.Content != "" {
quoted := strconv.Quote(op.Content)
op.Content = quoted[1 : len(quoted)-1]View on GitHub (pinned to e2d772072e)
Solutions
- Check the wrapped %v detail for the underlying Postgres error code and fix the root cause
- Ensure the caller did not ignore an earlier error on the same tx (Postgres aborts subsequent statements after one fails)
- Verify the users row exists for the userId being incremented
- Add connection-pool health checks and reasonable statement_timeout settings
- Retry the transaction at the caller level on transient connection errors
Example fix
// before
_, err := tx.Exec("UPDATE users SET num_non_draft_plans = num_non_draft_plans + 1 WHERE id = $1", userId)
if err != nil {
return fmt.Errorf("error updating user num_non_draft_plans: %v", err)
}
// after
res, err := tx.Exec("UPDATE users SET num_non_draft_plans = num_non_draft_plans + 1 WHERE id = $1", userId)
if err != nil {
return fmt.Errorf("error updating user num_non_draft_plans: %w", err)
}
if n, _ := res.RowsAffected(); n == 0 {
return fmt.Errorf("user %s not found", userId)
} Defensive patterns
Strategy: try-catch
Validate before calling
var exists bool
err := db.Get(&exists, "SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)", userId)
if err != nil || !exists {
return fmt.Errorf("user %s not found, skipping increment", userId)
} Try / catch
if err := IncNumNonDraftPlans(userId, tx); err != nil {
var pqErr *pq.Error
if errors.As(err, &pqErr) && pqErr.Code == "25P02" {
// transaction aborted by earlier error; fix caller error handling
}
return err
} Prevention
- Always check every error on a tx before issuing further statements
- Validate userId exists before incrementing
- Set sane statement_timeout and pool health checks
- Wrap errors with %w so callers can errors.As the driver error
When it happens
Trigger: tx.Exec("UPDATE users SET num_non_draft_plans = num_non_draft_plans + 1 WHERE id = $1", userId) fails: connection dropped mid-transaction, transaction already aborted by a prior error, invalid user id (no row matched, not an error but worth noting), or Postgres error (lock timeout, dead connection).
Common situations: Long-lived transactions that exceed statement_timeout; a tx passed in after a previous statement failed (Postgres aborts the whole transaction); DB restart or failover during plan creation; passing an empty or stale userId from a deleted user row.
Related errors
- error creating plan: %v
- error inserting lockable plan id: %v
- error creating invite: %v
- error deleting invite: %v
- error accepting invite: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/5dfe7bd199000b1a.
Report an issue: GitHub.