From 71e291edd8a6018e2dc51077db52c8e785158ec7 Mon Sep 17 00:00:00 2001 From: "Ishan (OpenClaw)" Date: Sat, 7 Feb 2026 09:05:39 +0000 Subject: [PATCH 1/4] fix(frontend): keep Clerk enabled check in sync with AuthProvider --- frontend/src/auth/clerk.tsx | 23 +++++++++++++++---- .../src/components/providers/AuthProvider.tsx | 18 ++------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/frontend/src/auth/clerk.tsx b/frontend/src/auth/clerk.tsx index 691b3a0..fed6cf2 100644 --- a/frontend/src/auth/clerk.tsx +++ b/frontend/src/auth/clerk.tsx @@ -17,11 +17,26 @@ import { import type { ComponentProps } from "react"; +export function isValidClerkPublishableKey(key: string | undefined): key is string { + if (!key) return false; + // Clerk publishable keys look like: pk_test_... or pk_live_... + // In CI we want builds to stay secretless; if the key isn't present/valid, + // we skip Clerk entirely so `next build` can prerender. + // + // Note: Clerk appears to validate key *contents*, not just shape. We therefore + // use a conservative heuristic to avoid treating obvious placeholders as valid. + 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 isClerkEnabled(): boolean { - // Invariant: Clerk is disabled ONLY when the publishable key is absent. - // If a key is present, we assume Clerk is intended to be enabled and we let - // Clerk fail fast if the key is invalid/misconfigured. - return Boolean(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY); + // IMPORTANT: keep this in sync with AuthProvider; otherwise components like + // may render without a and crash during prerender. + return isValidClerkPublishableKey(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY); } export function SignedIn(props: { children: ReactNode }) { diff --git a/frontend/src/components/providers/AuthProvider.tsx b/frontend/src/components/providers/AuthProvider.tsx index 4da743e..30000cf 100644 --- a/frontend/src/components/providers/AuthProvider.tsx +++ b/frontend/src/components/providers/AuthProvider.tsx @@ -3,26 +3,12 @@ import { ClerkProvider } from "@clerk/nextjs"; import type { ReactNode } from "react"; -function isLikelyValidClerkPublishableKey(key: string | undefined): key is string { - if (!key) return false; - // Clerk publishable keys look like: pk_test_... or pk_live_... - // In CI we want builds to stay secretless; if the key isn't present/valid, - // we skip Clerk entirely so `next build` can prerender. - // - // Note: Clerk appears to validate key *contents*, not just shape. We therefore - // use a conservative heuristic to avoid treating obvious placeholders as valid. - 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; -} +import { isValidClerkPublishableKey } from "@/auth/clerk"; export function AuthProvider({ children }: { children: ReactNode }) { const publishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY; - if (!isLikelyValidClerkPublishableKey(publishableKey)) { + if (!isValidClerkPublishableKey(publishableKey)) { return <>{children}; } From 795f40e50b7c1f6e58fa41f5a092c2867357ad45 Mon Sep 17 00:00:00 2001 From: "Ishan (OpenClaw)" Date: Sat, 7 Feb 2026 09:22:41 +0000 Subject: [PATCH 2/4] fix(typecheck): avoid targets shadowing in board memory notify --- backend/app/api/board_memory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app/api/board_memory.py b/backend/app/api/board_memory.py index e861ba1..3d9271c 100644 --- a/backend/app/api/board_memory.py +++ b/backend/app/api/board_memory.py @@ -110,8 +110,8 @@ async def _notify_chat_targets( # These are intended to be parsed verbatim by agent runtimes. if command in {"/pause", "/resume"}: statement = select(Agent).where(col(Agent.board_id) == board.id) - targets = list(await session.exec(statement)) - for agent in targets: + pause_targets: list[Agent] = list(await session.exec(statement)) + for agent in pause_targets: if actor.actor_type == "agent" and actor.agent and agent.id == actor.agent.id: continue if not agent.openclaw_session_id: From fd954238ea1129b2e20d9d5e6ac82ec0ada9141c Mon Sep 17 00:00:00 2001 From: "Ishan (OpenClaw)" Date: Sat, 7 Feb 2026 10:09:21 +0000 Subject: [PATCH 3/4] refactor(frontend): share Clerk publishable-key heuristic across client+server --- frontend/src/auth/clerk.tsx | 20 ++++------------- frontend/src/auth/clerkKey.ts | 22 +++++++++++++++++++ .../src/components/providers/AuthProvider.tsx | 4 ++-- frontend/src/proxy.ts | 5 ++++- 4 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 frontend/src/auth/clerkKey.ts diff --git a/frontend/src/auth/clerk.tsx b/frontend/src/auth/clerk.tsx index fed6cf2..d8d270c 100644 --- a/frontend/src/auth/clerk.tsx +++ b/frontend/src/auth/clerk.tsx @@ -17,26 +17,14 @@ import { import type { ComponentProps } from "react"; -export function isValidClerkPublishableKey(key: string | undefined): key is string { - if (!key) return false; - // Clerk publishable keys look like: pk_test_... or pk_live_... - // In CI we want builds to stay secretless; if the key isn't present/valid, - // we skip Clerk entirely so `next build` can prerender. - // - // Note: Clerk appears to validate key *contents*, not just shape. We therefore - // use a conservative heuristic to avoid treating obvious placeholders as valid. - 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; -} +import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey"; export function isClerkEnabled(): boolean { // IMPORTANT: keep this in sync with AuthProvider; otherwise components like // may render without a and crash during prerender. - return isValidClerkPublishableKey(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY); + return isLikelyValidClerkPublishableKey( + process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, + ); } export function SignedIn(props: { children: ReactNode }) { diff --git a/frontend/src/auth/clerkKey.ts b/frontend/src/auth/clerkKey.ts new file mode 100644 index 0000000..3e36186 --- /dev/null +++ b/frontend/src/auth/clerkKey.ts @@ -0,0 +1,22 @@ +// Shared Clerk publishable-key gating logic. +// +// IMPORTANT: keep this file dependency-free (no `"use client"`, no React, no Clerk imports) +// so it can be used from both client and server/edge entrypoints. + +export function isLikelyValidClerkPublishableKey(key: string | undefined): key is string { + if (!key) return false; + + // Clerk publishable keys look like: pk_test_... or pk_live_... + // In CI we want builds to stay secretless; if the key isn't present/valid, + // we skip Clerk entirely so `next build` can prerender. + // + // Note: this is a conservative heuristic (not an authoritative validation). + 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; +} diff --git a/frontend/src/components/providers/AuthProvider.tsx b/frontend/src/components/providers/AuthProvider.tsx index 30000cf..24234de 100644 --- a/frontend/src/components/providers/AuthProvider.tsx +++ b/frontend/src/components/providers/AuthProvider.tsx @@ -3,12 +3,12 @@ import { ClerkProvider } from "@clerk/nextjs"; import type { ReactNode } from "react"; -import { isValidClerkPublishableKey } from "@/auth/clerk"; +import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey"; export function AuthProvider({ children }: { children: ReactNode }) { const publishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY; - if (!isValidClerkPublishableKey(publishableKey)) { + if (!isLikelyValidClerkPublishableKey(publishableKey)) { return <>{children}; } diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts index 75b3086..429d9ae 100644 --- a/frontend/src/proxy.ts +++ b/frontend/src/proxy.ts @@ -1,7 +1,10 @@ import { NextResponse } from "next/server"; import { clerkMiddleware } from "@clerk/nextjs/server"; -const isClerkEnabled = () => Boolean(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY); +import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey"; + +const isClerkEnabled = () => + isLikelyValidClerkPublishableKey(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY); export default isClerkEnabled() ? clerkMiddleware() : () => NextResponse.next(); From 70bef66132e0a7af47cc40c73792f5cf7bb28574 Mon Sep 17 00:00:00 2001 From: "Ishan (OpenClaw)" Date: Sat, 7 Feb 2026 10:11:13 +0000 Subject: [PATCH 4/4] chore(lint): remove trailing blank line in new backend modules --- backend/app/schemas/gateway_coordination.py | 1 - backend/app/services/board_leads.py | 1 - 2 files changed, 2 deletions(-) diff --git a/backend/app/schemas/gateway_coordination.py b/backend/app/schemas/gateway_coordination.py index 55a5b1e..a3f6744 100644 --- a/backend/app/schemas/gateway_coordination.py +++ b/backend/app/schemas/gateway_coordination.py @@ -72,4 +72,3 @@ class GatewayLeadBroadcastResponse(SQLModel): sent: int = 0 failed: int = 0 results: list[GatewayLeadBroadcastBoardResult] = Field(default_factory=list) - diff --git a/backend/app/services/board_leads.py b/backend/app/services/board_leads.py index 89208a7..ba39e6d 100644 --- a/backend/app/services/board_leads.py +++ b/backend/app/services/board_leads.py @@ -105,4 +105,3 @@ async def ensure_board_lead_agent( pass return agent, True -