plandex-ai/plandex · error

error validating plan membership

Error message

error validating plan membership

What it means

This 500 is returned by authorizePlan when db.ValidatePlanAccess(planId, userId, orgId) returns a non-nil error, meaning the membership/access check itself failed at the database level. It does not mean the user lacks access (that yields 'no access to plan'); it means the server could not evaluate access. The real cause is logged as 'error validating plan membership: %v'.

Source

Thrown at app/server/handlers/auth_helpers.go:676

	}

	if !auth.HasPermission(shared.PermissionDeleteAnyProject) {
		log.Println("User does not have permission to delete project")
		http.Error(w, "User does not have permission to delete project", http.StatusForbidden)
		return false
	}

	return true
}

func authorizePlan(w http.ResponseWriter, planId string, auth *types.ServerAuth) *db.Plan {
	log.Println("authorizing plan")

	plan, err := db.ValidatePlanAccess(planId, auth.User.Id, auth.OrgId)

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

	if plan == nil {
		log.Println("user doesn't have access the plan")
		http.Error(w, "no access to plan", http.StatusUnauthorized)
		return nil
	}

	return plan
}

func authorizePlanUpdate(w http.ResponseWriter, planId string, auth *types.ServerAuth) *db.Plan {
	plan := authorizePlan(w, planId, auth)

	if plan == nil {
		return nil
	}

View on GitHub (pinned to e2d772072e)

Solutions

  1. Inspect server logs for 'error validating plan membership:' to read the underlying DB error
  2. Verify database connectivity and that the plans/plan-membership schema migrations are applied
  3. Ensure the planId in the request is well-formed (valid ID/UUID) before calling the API
  4. Retry the request after resolving the transient DB condition
Defensive patterns

Strategy: retry

Validate before calling

function assertValidPlanId(planId) {
  const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  if (!uuidRe.test(planId)) throw new Error(`Malformed planId: ${planId}`);
}

Type guard

function isPlanAccessLookupError(res) {
  return res.status === 500;
}

Try / catch

try {
  const res = await fetch(`/api/plans/${planId}/branches`, { headers: authHeaders });
  if (res.status === 500) {
    // membership lookup failed server-side (DB issue): retry with backoff
    await sleep(1500);
    return retryFetch();
  }
  return await res.json();
} catch (e) { throw e; }

Prevention

When it happens

Trigger: Any caller (authorizePlanUpdate/Delete/Rename/Archive, ListBranchesHandler, CreateBranchHandler) hitting a DB error inside ValidatePlanAccess: connection failure, malformed planId breaking the query, missing plan_members/plans table or column after schema change, or query timeout.

Common situations: Database outage or pool exhaustion; a migration adding a membership column was not applied; a corrupted/non-UUID planId causes the query to error instead of returning nil; read-replica lag causing transient query failures.

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/b7816485a5a96548. Report an issue: GitHub.