plandex-ai/plandex · error

error incrementing num non draft plans: %v

Error message

error incrementing num non draft plans: %v

What it means

This error wraps a failure from db.IncNumNonDraftPlans, the second statement in the same 'rename plan' transaction, which increments the user's count of non-draft plans after a successful rename. If this UPDATE fails the transaction is rolled back (the rename is undone too) and the wrapped error is returned. It indicates the bookkeeping counter could not be updated even though the rename itself may have succeeded inside the tx.

Source

Thrown at app/server/model/plan/tell_load.go:125

				if err != nil {
					log.Printf("Error generating plan name: %v\n", err)
					errCh <- fmt.Errorf("error generating plan name: %v", err)
					return
				}

				err = db.WithTx(active.Ctx, "rename plan", func(tx *sqlx.Tx) error {
					err := db.RenamePlan(planId, name, tx)

					if err != nil {
						log.Printf("Error renaming plan: %v\n", err)
						return fmt.Errorf("error renaming plan: %v", err)
					}

					err = db.IncNumNonDraftPlans(currentUserId, tx)

					if err != nil {
						log.Printf("Error incrementing num non draft plans: %v\n", err)
						return fmt.Errorf("error incrementing num non draft plans: %v", err)
					}

					return nil
				})

				if err != nil {
					log.Printf("Error renaming plan: %v\n", err)
					errCh <- fmt.Errorf("error renaming plan: %v", err)
					return
				}
			}

			errCh <- nil
		}()

		go func() {
			defer func() {
				if r := recover(); r != nil {

View on GitHub (pinned to e2d772072e)

Solutions

  1. Inspect the wrapped inner error and 'Error incrementing num non draft plans' log to find the root SQL cause
  2. Retry the request — the transaction rolled back so the draft is intact and the whole rename can be redone
  3. Verify the user-stats row for currentUserId exists and the IncNumNonDraftPlans SQL matches the current schema
  4. Check DB logs for deadlocks or lock waits on the user row during the transaction
  5. Wrap the flow with a bounded retry for transient connection/deadlock errors
Defensive patterns

Strategy: try-catch

Validate before calling

// before the transaction
var exists bool
err := db.Get(&exists, "SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)", currentUserId)
if err != nil || !exists {
    return fmt.Errorf("user %s not found; cannot increment non-draft plan count", currentUserId)
}

Type guard

func incErrIsRollbackSafe(err error) bool {
    // WithTx rolls back on any returned error; classify for retry decisions
    var pgErr *pgconn.PgError
    return errors.As(err, &pgErr) && (pgErr.Code == "40001" || pgErr.Code == "40P01")
}

Try / catch

err = db.WithTx(active.Ctx, "rename plan", func(tx *sqlx.Tx) error {
    if err := db.RenamePlan(planId, name, tx); err != nil {
        return err
    }
    if err := db.IncNumNonDraftPlans(currentUserId, tx); err != nil {
        return fmt.Errorf("error incrementing num non draft plans: %w", err)
    }
    return nil
})
if err != nil {
    log.Printf("rename tx rolled back: %v", err)
    return err
}

Prevention

When it happens

Trigger: After a successful db.RenamePlan inside db.WithTx, db.IncNumNonDraftPlans(currentUserId, tx) fails — typically a connection loss between statements, a constraint violation or missing row for currentUserId in the user-stats table, or SQL mismatch after schema changes.

Common situations: User row/statistics table missing or migrated (column renamed), DB connection pool exhausted mid-transaction, deadlock with another transaction touching the same user row, statement timeout on a busy table.

Related errors


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