plandex-ai/plandex · warning

Not found

Error message

Not found

What it means

DeletePlanHandler (plans_crud.go:250) returns 404 'Not found' when the DELETE statement affected zero rows, meaning no plan row with the given planId existed in the plans table. Note: authorization already passed (the plan appeared to exist for this user), so this usually indicates a race or an ID mismatch rather than a normal missing-plan case.

Source

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

	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)

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

	log.Println("Successfully deleted plan", planId)
}

func DeleteAllPlansHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Received request for DeleteAllPlansHandler")

	auth := Authenticate(w, r, true)

View on GitHub (pinned to e2d772072e)

Solutions

  1. Treat 404 as success on retry/delete flows — if the goal was deletion, the plan is already gone; refresh the plan list.
  2. Verify the planId in the request URL matches an existing row (SELECT id FROM plans WHERE id = ...).
  3. Confirm the server is connected to the expected database/environment (check DATABASE_URL / db config).
  4. Add a UNIQUE guard or soft-delete flag if concurrent deletes of the same plan are common in your app.

Example fix

// client-side before
await api.deletePlan(planId); // 404 throws
// after
const res = await api.deletePlan(planId);
if (res.status === 404) return; // already deleted — success for idempotent delete
Defensive patterns

Strategy: fallback

Validate before calling

const exists = await fetch(`/api/plans/${planId}`).then(r => r.status !== 404);
if (!exists) return; // nothing to delete

Type guard

func planExists(ctx context.Context, db *sql.DB, planId string) (bool, error) {
    var one int
    err := db.QueryRowContext(ctx, "SELECT 1 FROM plans WHERE id = $1", planId).Scan(&one)
    if errors.Is(err, sql.ErrNoRows) { return false, nil }
    return err == nil, err
}

Try / catch

const res = await fetch(`/api/plans/${planId}`, { method: 'DELETE' });
if (res.status === 404) {
  // already deleted — treat as success in idempotent delete flows
} else if (!res.ok) {
  throw new Error(await res.text());
}

Prevention

When it happens

Trigger: DELETE FROM plans WHERE id = $1 matches 0 rows: the plan was deleted by another user/request between authorizePlanDelete and the DELETE, the planId path variable is wrong or points to an already-purged plan, or the row lives in a different database/environment than expected.

Common situations: Double-clicking a delete button so the second request 404s; stale UI listing a plan deleted elsewhere; pointing a dev client at a prod (or empty) database; IDs from a different environment after a DB reset.

Related errors


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