plandex-ai/plandex · error

Error deleting plan:

Error message

Error deleting plan: 

What it means

DeletePlanHandler returns this 500 when the DELETE FROM plans SQL statement fails. Authorization succeeded but the database delete errored; the raw database error is appended to the response body. Referential integrity issues (foreign keys from branches/context tables) are a classic cause.

Source

Thrown at app/server/handlers/plans_crud.go:237

	log.Println("planId: ", planId)

	plan := authorizePlanDelete(w, planId, auth)

	if plan == nil {
		return
	}

	if plan.OwnerId != auth.User.Id {
		log.Println("Only the plan owner can delete a plan")
		http.Error(w, "Only the plan owner can delete a plan", http.StatusForbidden)
		return
	}

	res, err := db.Conn.Exec("DELETE FROM plans WHERE id = $1", planId)

	if err != nil {
		log.Printf("Error deleting plan: %v\n", err)
		http.Error(w, "Error deleting plan: "+err.Error(), http.StatusInternalServerError)
		return
	}

	rowsAffected, err := res.RowsAffected()
	if err != nil {
		log.Printf("Error getting rows affected: %v\n", err)
		http.Error(w, "Error getting rows affected: "+err.Error(), http.StatusInternalServerError)
		return
	}

	if rowsAffected == 0 {
		log.Println("Plan not found")
		http.Error(w, "Not found", http.StatusNotFound)
		return
	}

	err = db.DeletePlanDir(auth.OrgId, planId)

View on GitHub (pinned to e2d772072e)

Solutions

  1. Read the wrapped database error in the response/server log — a 23503 foreign-key violation points to dependent rows
  2. Add ON DELETE CASCADE (or delete children first) for tables referencing plans in the migration
  3. Confirm the database role used by the server has DELETE privilege on plans
  4. Retry on transient errors; investigate locks with pg_locks if a deadlock/timeout is reported

Example fix

// before
ALTER TABLE contexts ADD CONSTRAINT fk_plans FOREIGN KEY (plan_id) REFERENCES plans(id);
// after
ALTER TABLE contexts ADD CONSTRAINT fk_plans FOREIGN KEY (plan_id) REFERENCES plans(id) ON DELETE CASCADE;
Defensive patterns

Strategy: retry

Validate before calling

if _, err := db.Conn.Exec("SELECT 1 FROM plans WHERE id=$1 FOR UPDATE", planId); err != nil { return err } // row lockable/exists

Type guard

func isFKViolation(err error) bool { var pgErr *pgconn.PgError; return errors.As(err, &pgErr) && pgErr.Code == "23503" }

Try / catch

res, err := db.Conn.Exec("DELETE FROM plans WHERE id = $1", planId)
if err != nil {
	if isFKViolation(err) { /* delete dependent rows or rely on CASCADE */ return }
	if isTransientDBErr(err) { /* retry with backoff */ return }
	http.Error(w, "Error deleting plan", http.StatusInternalServerError)
	return
}

Prevention

When it happens

Trigger: DELETE plan request where db.Conn.Exec("DELETE FROM plans WHERE id = $1", planId) fails: foreign-key constraint violation from dependent rows without ON DELETE CASCADE, DB unreachable, lock timeout, or permission denied for the DB role.

Common situations: Schema lacking CASCADE on tables referencing plans (contexts, branches, shares); Postgres role granted SELECT/INSERT but not DELETE; connection pool exhaustion; deadlock with a concurrent operation holding a row lock.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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