diff --git a/frontend/src/app/agents/[agentId]/edit/page.tsx b/frontend/src/app/agents/[agentId]/edit/page.tsx
index 377a775c..36bbc357 100644
--- a/frontend/src/app/agents/[agentId]/edit/page.tsx
+++ b/frontend/src/app/agents/[agentId]/edit/page.tsx
@@ -3,7 +3,7 @@
import { useMemo, useState } from "react";
import { useParams, useRouter } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import { ApiError } from "@/api/mutator";
import {
diff --git a/frontend/src/app/agents/[agentId]/page.tsx b/frontend/src/app/agents/[agentId]/page.tsx
index 29706e31..cfe07319 100644
--- a/frontend/src/app/agents/[agentId]/page.tsx
+++ b/frontend/src/app/agents/[agentId]/page.tsx
@@ -4,7 +4,7 @@ import { useMemo, useState } from "react";
import Link from "next/link";
import { useParams, useRouter } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import { ApiError } from "@/api/mutator";
import {
diff --git a/frontend/src/app/agents/new/page.tsx b/frontend/src/app/agents/new/page.tsx
index 92fec06f..69c0378d 100644
--- a/frontend/src/app/agents/new/page.tsx
+++ b/frontend/src/app/agents/new/page.tsx
@@ -3,7 +3,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import { ApiError } from "@/api/mutator";
import {
diff --git a/frontend/src/app/agents/page.tsx b/frontend/src/app/agents/page.tsx
index ea5cb450..e718e27e 100644
--- a/frontend/src/app/agents/page.tsx
+++ b/frontend/src/app/agents/page.tsx
@@ -4,7 +4,7 @@ import { useMemo, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import {
type ColumnDef,
type SortingState,
diff --git a/frontend/src/app/boards/[boardId]/approvals/page.tsx b/frontend/src/app/boards/[boardId]/approvals/page.tsx
index a362a64d..1bf60bde 100644
--- a/frontend/src/app/boards/[boardId]/approvals/page.tsx
+++ b/frontend/src/app/boards/[boardId]/approvals/page.tsx
@@ -2,7 +2,7 @@
import { useParams } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut } from "@/auth/clerk";
import { BoardApprovalsPanel } from "@/components/BoardApprovalsPanel";
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
diff --git a/frontend/src/app/boards/[boardId]/edit/page.tsx b/frontend/src/app/boards/[boardId]/edit/page.tsx
index a3f98133..7f147ac6 100644
--- a/frontend/src/app/boards/[boardId]/edit/page.tsx
+++ b/frontend/src/app/boards/[boardId]/edit/page.tsx
@@ -3,7 +3,7 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useParams, useRouter, useSearchParams } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import { X } from "lucide-react";
import { ApiError } from "@/api/mutator";
diff --git a/frontend/src/app/boards/[boardId]/page.tsx b/frontend/src/app/boards/[boardId]/page.tsx
index 11a24cc3..c5bfcd8a 100644
--- a/frontend/src/app/boards/[boardId]/page.tsx
+++ b/frontend/src/app/boards/[boardId]/page.tsx
@@ -3,7 +3,7 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useParams, useRouter } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import {
Activity,
ArrowUpRight,
diff --git a/frontend/src/app/boards/new/page.tsx b/frontend/src/app/boards/new/page.tsx
index c9fb9ae6..2d851d39 100644
--- a/frontend/src/app/boards/new/page.tsx
+++ b/frontend/src/app/boards/new/page.tsx
@@ -4,7 +4,7 @@ import { useMemo, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import { ApiError } from "@/api/mutator";
import { useCreateBoardApiV1BoardsPost } from "@/api/generated/boards/boards";
diff --git a/frontend/src/app/boards/page.tsx b/frontend/src/app/boards/page.tsx
index 2234ef87..b4c96a67 100644
--- a/frontend/src/app/boards/page.tsx
+++ b/frontend/src/app/boards/page.tsx
@@ -3,7 +3,7 @@
import { useMemo, useState } from "react";
import Link from "next/link";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import {
type ColumnDef,
flexRender,
diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx
index be3ef59d..f32dfce9 100644
--- a/frontend/src/app/dashboard/page.tsx
+++ b/frontend/src/app/dashboard/page.tsx
@@ -2,7 +2,7 @@
import { useMemo } from "react";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import {
Area,
AreaChart,
diff --git a/frontend/src/app/gateways/[gatewayId]/edit/page.tsx b/frontend/src/app/gateways/[gatewayId]/edit/page.tsx
index 9cd78a80..33046791 100644
--- a/frontend/src/app/gateways/[gatewayId]/edit/page.tsx
+++ b/frontend/src/app/gateways/[gatewayId]/edit/page.tsx
@@ -3,7 +3,7 @@
import { useState } from "react";
import { useParams, useRouter } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import { CheckCircle2, RefreshCcw, XCircle } from "lucide-react";
import { ApiError } from "@/api/mutator";
diff --git a/frontend/src/app/gateways/[gatewayId]/page.tsx b/frontend/src/app/gateways/[gatewayId]/page.tsx
index a8f4133c..3aad93bd 100644
--- a/frontend/src/app/gateways/[gatewayId]/page.tsx
+++ b/frontend/src/app/gateways/[gatewayId]/page.tsx
@@ -3,7 +3,7 @@
import { useMemo } from "react";
import { useParams, useRouter } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import { ApiError } from "@/api/mutator";
import {
diff --git a/frontend/src/app/gateways/new/page.tsx b/frontend/src/app/gateways/new/page.tsx
index f310fd73..dea970cc 100644
--- a/frontend/src/app/gateways/new/page.tsx
+++ b/frontend/src/app/gateways/new/page.tsx
@@ -3,7 +3,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import { CheckCircle2, RefreshCcw, XCircle } from "lucide-react";
import { ApiError } from "@/api/mutator";
diff --git a/frontend/src/app/gateways/page.tsx b/frontend/src/app/gateways/page.tsx
index ca5ac186..b60c0adb 100644
--- a/frontend/src/app/gateways/page.tsx
+++ b/frontend/src/app/gateways/page.tsx
@@ -3,7 +3,7 @@
import { useMemo, useState } from "react";
import Link from "next/link";
-import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk";
import {
type ColumnDef,
type SortingState,
diff --git a/frontend/src/app/onboarding/page.tsx b/frontend/src/app/onboarding/page.tsx
index 21bd9361..24a934ce 100644
--- a/frontend/src/app/onboarding/page.tsx
+++ b/frontend/src/app/onboarding/page.tsx
@@ -3,7 +3,7 @@
import { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/navigation";
-import { SignInButton, SignedIn, SignedOut, useAuth, useUser } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut, useAuth, useUser } from "@/auth/clerk";
import { Globe, Info, RotateCcw, Save, User } from "lucide-react";
import { ApiError } from "@/api/mutator";
diff --git a/frontend/src/auth/clerk.tsx b/frontend/src/auth/clerk.tsx
new file mode 100644
index 00000000..d5ee1dca
--- /dev/null
+++ b/frontend/src/auth/clerk.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import type { ReactNode } from "react";
+
+// NOTE: We intentionally keep this file very small and dependency-free.
+// It provides CI/secretless-build safe fallbacks for Clerk hooks/components.
+
+import {
+ ClerkProvider,
+ SignedIn as ClerkSignedIn,
+ SignedOut as ClerkSignedOut,
+ SignInButton as ClerkSignInButton,
+ SignOutButton as ClerkSignOutButton,
+ useAuth as clerkUseAuth,
+ useUser as clerkUseUser,
+} from "@clerk/nextjs";
+
+export function isClerkEnabled(): boolean {
+ const key = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
+ if (!key) return false;
+
+ // Clerk validates publishable key contents at runtime; use a conservative heuristic.
+ const m = /^pk_(test|live)_([A-Za-z0-9]+)$/.exec(key);
+ if (!m) return false;
+ const body = m[2];
+ if (body.length < 16) return false;
+ if (/^0+$/.test(body)) return false;
+ return true;
+}
+
+export function SignedIn(props: { children: ReactNode }) {
+ if (!isClerkEnabled()) return null;
+ return {props.children};
+}
+
+export function SignedOut(props: { children: ReactNode }) {
+ if (!isClerkEnabled()) return <>{props.children}>;
+ return {props.children};
+}
+
+// Accept arbitrary Clerk component props so existing call sites don't need edits.
+export function SignInButton(props: any) {
+ if (!isClerkEnabled()) return null;
+ return ;
+}
+
+export function SignOutButton(props: any) {
+ if (!isClerkEnabled()) return null;
+ return ;
+}
+
+export function useUser() {
+ if (!isClerkEnabled()) {
+ return { isLoaded: true, isSignedIn: false, user: null } as const;
+ }
+ return clerkUseUser();
+}
+
+export function useAuth() {
+ if (!isClerkEnabled()) {
+ return {
+ isLoaded: true,
+ isSignedIn: false,
+ userId: null,
+ sessionId: null,
+ getToken: async () => null,
+ } as const;
+ }
+ return clerkUseAuth();
+}
+
+// Re-export ClerkProvider for places that want to mount it, but strongly prefer
+// gating via isClerkEnabled() at call sites.
+export { ClerkProvider };
diff --git a/frontend/src/components/BoardApprovalsPanel.tsx b/frontend/src/components/BoardApprovalsPanel.tsx
index 0ed470d4..264d73c5 100644
--- a/frontend/src/components/BoardApprovalsPanel.tsx
+++ b/frontend/src/components/BoardApprovalsPanel.tsx
@@ -2,7 +2,7 @@
import { useCallback, useMemo, useState } from "react";
-import { useAuth } from "@clerk/nextjs";
+import { useAuth } from "@/auth/clerk";
import { useQueryClient } from "@tanstack/react-query";
import { Clock } from "lucide-react";
diff --git a/frontend/src/components/organisms/LandingHero.tsx b/frontend/src/components/organisms/LandingHero.tsx
index 38db84f0..349bd48a 100644
--- a/frontend/src/components/organisms/LandingHero.tsx
+++ b/frontend/src/components/organisms/LandingHero.tsx
@@ -1,6 +1,6 @@
"use client";
-import { SignInButton, SignedIn, SignedOut } from "@clerk/nextjs";
+import { SignInButton, SignedIn, SignedOut } from "@/auth/clerk";
import { HeroCopy } from "@/components/molecules/HeroCopy";
import { Button } from "@/components/ui/button";
diff --git a/frontend/src/components/organisms/UserMenu.tsx b/frontend/src/components/organisms/UserMenu.tsx
index 09189244..661fbeab 100644
--- a/frontend/src/components/organisms/UserMenu.tsx
+++ b/frontend/src/components/organisms/UserMenu.tsx
@@ -1,7 +1,7 @@
"use client";
import Image from "next/image";
-import { SignOutButton, useUser } from "@clerk/nextjs";
+import { SignOutButton, useUser } from "@/auth/clerk";
import { LogOut } from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
diff --git a/frontend/src/components/templates/DashboardShell.tsx b/frontend/src/components/templates/DashboardShell.tsx
index ace048aa..04b516aa 100644
--- a/frontend/src/components/templates/DashboardShell.tsx
+++ b/frontend/src/components/templates/DashboardShell.tsx
@@ -2,7 +2,7 @@
import type { ReactNode } from "react";
-import { SignedIn, useUser } from "@clerk/nextjs";
+import { SignedIn, useUser } from "@/auth/clerk";
import { BrandMark } from "@/components/atoms/BrandMark";
import { UserMenu } from "@/components/organisms/UserMenu";
diff --git a/frontend/src/components/templates/LandingShell.tsx b/frontend/src/components/templates/LandingShell.tsx
index 59760fc4..32d6af7a 100644
--- a/frontend/src/components/templates/LandingShell.tsx
+++ b/frontend/src/components/templates/LandingShell.tsx
@@ -2,7 +2,7 @@
import type { ReactNode } from "react";
-import { SignedIn } from "@clerk/nextjs";
+import { SignedIn } from "@/auth/clerk";
import { BrandMark } from "@/components/atoms/BrandMark";
import { UserMenu } from "@/components/organisms/UserMenu";
diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts
index 7543980c..41a0eb80 100644
--- a/frontend/src/proxy.ts
+++ b/frontend/src/proxy.ts
@@ -1,6 +1,18 @@
+import { NextResponse } from "next/server";
import { clerkMiddleware } from "@clerk/nextjs/server";
-export default clerkMiddleware();
+const isClerkEnabled = () => {
+ const key = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
+ if (!key) return false;
+ const m = /^pk_(test|live)_([A-Za-z0-9]+)$/.exec(key);
+ if (!m) return false;
+ const body = m[2];
+ if (body.length < 16) return false;
+ if (/^0+$/.test(body)) return false;
+ return true;
+};
+
+export default isClerkEnabled() ? clerkMiddleware() : () => NextResponse.next();
export const config = {
matcher: [