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
- Read the wrapped database error in the response/server log — a 23503 foreign-key violation points to dependent rows
- Add ON DELETE CASCADE (or delete children first) for tables referencing plans in the migration
- Confirm the database role used by the server has DELETE privilege on plans
- 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
- Define foreign keys referencing plans with ON DELETE CASCADE
- Grant the server's DB role DELETE privilege on the plans table
- Keep statement timeouts generous enough and retry transient failures
- Investigate pg_locks for deadlocks when deletes run alongside active plan operations
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
- error deleting invite: %v
- error removing all locks: %v
- error deleting model pack: %v
- error deleting draft plans: %v
- error deleting plans: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/13997a39f690508c.
Report an issue: GitHub.