plandex-ai/plandex · error

error inserting lockable plan id: %v

Error message

error inserting lockable plan id: %v

What it means

This error wraps a failure of the INSERT INTO lockable_plan_ids (plan_id) VALUES ($1) statement inside CreatePlan (app/server/db/plan_helpers.go:65). Every plan must have a corresponding row in lockable_plan_ids so the plan-locking system can acquire locks on it. The plans row was already inserted successfully in the same transaction, so this failure rolls back the whole plan creation.

Source

Thrown at app/server/db/plan_helpers.go:65

			orgId,
			userId,
			projectId,
			name,
			planConfig,
		).Scan(
			&plan.Id,
			&plan.CreatedAt,
			&plan.UpdatedAt,
		)

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

		_, err = tx.Exec("INSERT INTO lockable_plan_ids (plan_id) VALUES ($1)", plan.Id)

		if err != nil {
			return fmt.Errorf("error inserting lockable plan id: %v", err)
		}

		// the one place where we do this to skip the locking queue
		// ok to cheat this once since we're creating a new plan
		repo := getGitRepo(orgId, plan.Id)
		_, err = CreateBranch(repo, plan, nil, "main", tx)

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

		log.Println("Created branch main")

		err = InitPlan(orgId, plan.Id)

		if err != nil {
			return fmt.Errorf("error initializing plan dir: %v", err)
		}

View on GitHub (pinned to e2d772072e)

Solutions

  1. Read the wrapped %v error for the pq error code; if it's 42P01 (undefined_table), run the migrations that create lockable_plan_ids
  2. If it's a unique_violation, check for orphaned lockable_plan_ids rows or a trigger re-inserting the id and clean up
  3. Verify DB connectivity and transaction timeouts; retry CreatePlan if the failure was transient
  4. Confirm plan.Id was returned correctly by the prior RETURNING insert (an empty id causes FK/duplicate problems)

Example fix

// before
_, err = tx.Exec("INSERT INTO lockable_plan_ids (plan_id) VALUES ($1)", plan.Id)
if err != nil {
    return fmt.Errorf("error inserting lockable plan id: %v", err)
}
// after (idempotent against stale rows)
_, err = tx.Exec("INSERT INTO lockable_plan_ids (plan_id) VALUES ($1) ON CONFLICT (plan_id) DO NOTHING", plan.Id)
if err != nil {
    return fmt.Errorf("error inserting lockable plan id: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: confirm the lockable_plan_ids table exists before creating plans
var exists bool
err := Conn.Get(&exists, `SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'lockable_plan_ids')`)
if err != nil || !exists {
    return fmt.Errorf("lockable_plan_ids table missing; run migrations")
}

Type guard

func isUndefinedTable(err error) bool {
    var pqErr *pq.Error
    return errors.As(err, &pqErr) && pqErr.Code == "42P01"
}

Try / catch

plan, err := db.CreatePlan(ctx, orgId, projectId, userId, name)
if err != nil {
    var pqErr *pq.Error
    if errors.As(err, &pqErr) {
        switch pqErr.Code {
        case "42P01":
            return nil, fmt.Errorf("schema out of date, run migrations: %w", err)
        case "23505":
            return nil, fmt.Errorf("plan lock id already exists: %w", err)
        }
    }
    return nil, fmt.Errorf("create plan failed: %w", err)
}

Prevention

When it happens

Trigger: Calling CreatePlan when the lockable_plan_ids table is missing or has a different schema, when a row for the same plan_id already exists (duplicate key on the primary key), when the FK from lockable_plan_ids.plan_id to plans.id is violated by a trigger/deferred constraint, or when the DB connection drops mid-transaction.

Common situations: Schema drift after upgrading the server without running migrations (lockable_plan_ids table absent), stale data from a previous partial migration, or connection-pool/timeout issues under heavy plan-creation load.

Related errors


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