Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ VITE_FASTAPI_URL=http://localhost:8000

# Arcjet (server-only rate limiting / shield — copy from Arcjet dashboard)
ARCJET_KEY=

# LOCAL DEVELOPMENT ONLY — bypass Clerk entirely and sign in as a fixed dev user.
# Pair with ENABLE_DEV_AUTH on the Convex deployment and the FastAPI gateway.
# NEVER set this in production.
# VITE_ENABLE_DEV_AUTH=true
2 changes: 1 addition & 1 deletion apps/web/src/components/chat/chat-input.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useAuth } from "@clerk/tanstack-react-start";
import { useConvexMutation } from "@convex-dev/react-query";
import { api } from "@harness/convex-backend/convex/_generated/api";
import type { Id } from "@harness/convex-backend/convex/_generated/dataModel";
Expand Down Expand Up @@ -29,6 +28,7 @@ import React, {
useState,
} from "react";
import toast from "react-hot-toast";
import { useAuth } from "@/lib/auth";
import { useFileAttachments } from "../../hooks/use-file-attachments";
import {
AGENT_MODES,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/chat/conversation-kebab.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useUser } from "@clerk/tanstack-react-start";
import { useConvexMutation } from "@convex-dev/react-query";
import { api } from "@harness/convex-backend/convex/_generated/api";
import type { Id } from "@harness/convex-backend/convex/_generated/dataModel";
Expand All @@ -16,6 +15,7 @@ import {
} from "lucide-react";
import { useState } from "react";
import toast from "react-hot-toast";
import { useUser } from "@/lib/auth";
import {
buildShareUrl,
copyToClipboard,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/chat/settings-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useClerk, useUser } from "@clerk/tanstack-react-start";
import { convexQuery, useConvexMutation } from "@convex-dev/react-query";
import { api } from "@harness/convex-backend/convex/_generated/api";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useNavigate } from "@tanstack/react-router";
import { LogOut, User } from "lucide-react";
import { useClerk, useUser } from "@/lib/auth";
import { AgentConnections } from "../agent-connections";
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
import { Button } from "../ui/button";
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/chat/share-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useUser } from "@clerk/tanstack-react-start";
import { convexQuery, useConvexMutation } from "@convex-dev/react-query";
import { api } from "@harness/convex-backend/convex/_generated/api";
import type { Id } from "@harness/convex-backend/convex/_generated/dataModel";
Expand All @@ -15,6 +14,7 @@ import {
} from "lucide-react";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { useUser } from "@/lib/auth";
import {
buildShareUrl,
copyToClipboard,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useAuth, useClerk } from "@clerk/tanstack-react-start";
import { convexQuery } from "@convex-dev/react-query";
import { api } from "@harness/convex-backend/convex/_generated/api";
import { useQuery } from "@tanstack/react-query";
Expand All @@ -14,6 +13,7 @@ import {
SlidersHorizontal,
} from "lucide-react";
import { useMemo } from "react";
import { useAuth, useClerk } from "@/lib/auth";
import { useRegisterCommands } from "../../../hooks/use-register-commands";
import type { Command } from "../../../lib/command-palette/types";

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/harness-creation-assistant.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useAuth } from "@clerk/tanstack-react-start";
import { convexQuery, useConvexMutation } from "@convex-dev/react-query";
import { api } from "@harness/convex-backend/convex/_generated/api";
import { useMutation, useQuery } from "@tanstack/react-query";
Expand All @@ -15,6 +14,7 @@ import {
} from "lucide-react";
import { useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
import { useAuth } from "@/lib/auth";
import { env } from "../env";
import { PRESET_MCPS, presetIdsToServerEntries } from "../lib/mcp";
import { MODELS } from "../lib/models";
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/mcp-oauth-connect-row.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useAuth } from "@clerk/tanstack-react-start";
import { Server, Shield } from "lucide-react";
import { motion } from "motion/react";
import { useCallback, useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
import { useAuth } from "@/lib/auth";
import { env } from "../env";
import type { McpServerEntry } from "../lib/mcp";
import { RoseCurveSpinner } from "./rose-curve-spinner";
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/components/mcp-server-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { describe, expect, it, vi } from "vitest";
// The module pulls in Clerk + convex query hooks at import time; mock the
// heavier deps so the pure helper can be imported in isolation.
vi.mock("@clerk/tanstack-react-start", () => ({
// The `@/lib/auth` wrapper re-exports all of these from Clerk, so the mock
// must provide each one even though the component only uses useAuth.
useAuth: () => ({ getToken: async () => null }),
useUser: () => ({ isLoaded: true, isSignedIn: true, user: null }),
useClerk: () => ({ signOut: async () => {} }),
useReverification: <T>(fn: T) => fn,
}));
vi.mock("@convex-dev/react-query", () => ({
convexQuery: () => ({}),
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/mcp-server-status.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useAuth } from "@clerk/tanstack-react-start";
import { convexQuery, useConvexMutation } from "@convex-dev/react-query";
import { api } from "@harness/convex-backend/convex/_generated/api";
import type { Id } from "@harness/convex-backend/convex/_generated/dataModel";
Expand All @@ -17,6 +16,7 @@ import {
import { AnimatePresence, motion } from "motion/react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import toast from "react-hot-toast";
import { useAuth } from "@/lib/auth";
import { env } from "../env";
import {
fetchCommandsFromApi,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/princeton-connect-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import {
isClerkRuntimeError,
isReverificationCancelledError,
} from "@clerk/clerk-react/errors";
import { useReverification, useUser } from "@clerk/tanstack-react-start";
import { GraduationCap, Mail } from "lucide-react";
import { motion } from "motion/react";
import { useCallback, useState } from "react";
import toast from "react-hot-toast";
import { useReverification, useUser } from "@/lib/auth";
import type { McpServerEntry } from "../lib/mcp";
import { getPrincetonNetid } from "../lib/mcp";
import { RoseCurveSpinner } from "./rose-curve-spinner";
Expand Down
4 changes: 4 additions & 0 deletions apps/web/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export const env = createEnv({
VITE_CONVEX_URL: z.string().url(),
VITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),
VITE_FASTAPI_URL: z.string().url().optional(),
// LOCAL DEVELOPMENT ONLY: "true" bypasses Clerk entirely and signs you in
// as a fixed dev user. Pair with ENABLE_DEV_AUTH on the Convex deployment
// and the FastAPI gateway. Never set in production.
VITE_ENABLE_DEV_AUTH: z.enum(["true", "false"]).optional(),
},

/**
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/hooks/use-mcp-health-check.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useAuth } from "@clerk/tanstack-react-start";
import type { Id } from "@harness/convex-backend/convex/_generated/dataModel";
import { useCallback, useEffect, useRef, useState } from "react";
import { useAuth } from "@/lib/auth";
import type { HealthStatus } from "../components/mcp-server-status";
import { env } from "../env";
import type { McpAuthType } from "../lib/mcp";
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/hooks/use-rewind.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useAuth } from "@clerk/tanstack-react-start";
import { useConvexMutation } from "@convex-dev/react-query";
import { api } from "@harness/convex-backend/convex/_generated/api";
import type { Id } from "@harness/convex-backend/convex/_generated/dataModel";
import { useMutation } from "@tanstack/react-query";
import { useCallback } from "react";
import toast from "react-hot-toast";
import { useAuth } from "@/lib/auth";
import { useChatStreamContext } from "../lib/chat-stream-context";
import { resetAgentSessionForRewind } from "../lib/rewind";

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/hooks/use-workspace-credentials.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useAuth } from "@clerk/tanstack-react-start";
import { convexQuery, useConvexMutation } from "@convex-dev/react-query";
import { api } from "@harness/convex-backend/convex/_generated/api";
import type { Id } from "@harness/convex-backend/convex/_generated/dataModel";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useAuth } from "@/lib/auth";
import { env } from "../env";

const FASTAPI_URL = env.VITE_FASTAPI_URL ?? "http://localhost:8000";
Expand Down
63 changes: 63 additions & 0 deletions apps/web/src/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
useAuth as clerkUseAuth,
useClerk as clerkUseClerk,
useReverification as clerkUseReverification,
useUser as clerkUseUser,
} from "@clerk/tanstack-react-start";
import { env } from "../env";

/**
* Single switch for the loginless local-dev mode. When `VITE_ENABLE_DEV_AUTH` is
* "true", the app skips Clerk entirely and runs as a fixed dev user — pair it
* with `ENABLE_DEV_AUTH` on the Convex deployment and the FastAPI gateway.
*
* Off by default, and `DEV_AUTH` is a build-time constant, so the exported hooks
* below resolve to the REAL Clerk hooks in every normal build — this module is a
* pure pass-through unless a developer explicitly opts in. Import the auth hooks
* from here (`@/lib/auth`) instead of `@clerk/tanstack-react-start` so the
* bypass reaches every call site.
*/
export const DEV_AUTH = env.VITE_ENABLE_DEV_AUTH === "true";
Comment on lines +14 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEV_AUTH is not actually a build-time constant — dev stubs ship in every production bundle.

The comment claims DEV_AUTH is a build-time constant so the stubs are tree-shaken in production. This is not the case. Vite's static substitution only fires when the literal token sequence import.meta.env.VITE_* appears in source. Here DEV_AUTH is read via:

// env.ts
runtimeEnv: { ...import.meta.env, ... }  // spread — Vite does not enumerate keys through a spread

followed by createEnv(...) from @t3-oss/env-core — a runtime function call whose return value Vite cannot inline. So env.VITE_ENABLE_DEV_AUTH is a runtime property read, not a static token Vite replaces.

As a result:

  • The devUseAuth / devUseUser / devUseClerk / devUseReverification stubs are retained in every production bundle.
  • The useAuth = DEV_AUTH ? devUseAuth : clerkUseAuth ternaries are evaluated at runtime, not eliminated at build time.
  • A single VITE_ENABLE_DEV_AUTH=true at build time activates the full auth bypass (isSignedIn: true always, getToken returns "dev-auth") in production.

Fix: Read the env var directly in this file so Vite can inline it:

// Replace:
import { env } from "../env";
export const DEV_AUTH = env.VITE_ENABLE_DEV_AUTH === "true";

// With (Vite statically replaces import.meta.env.VITE_* at build time):
export const DEV_AUTH = import.meta.env.VITE_ENABLE_DEV_AUTH === "true";

With direct access, Vite inlines false for production builds (where the var is unset), allowing the bundler to dead-code-eliminate all four dev stubs.

See:

*
* Off by default, and `DEV_AUTH` is a build-time constant, so the exported hooks
* below resolve to the REAL Clerk hooks in every normal build — this module is a
* pure pass-through unless a developer explicitly opts in. Import the auth hooks
* from here (`@/lib/auth`) instead of `@clerk/tanstack-react-start` so the
* bypass reaches every call site.
*/
export const DEV_AUTH = env.VITE_ENABLE_DEV_AUTH === "true";
export const DEV_USER_ID = "dev-user";

export const DEV_USER_ID = "dev-user";

// Dev stubs — shapes mirror the subset of each Clerk hook's return the app reads.
const devUseAuth = (() => ({
isLoaded: true,
isSignedIn: true,
userId: DEV_USER_ID,
sessionId: "dev-session",
orgId: null,
orgRole: null,
getToken: async () => "dev-auth",
signOut: async () => {},
})) as unknown as typeof clerkUseAuth;

const devUseUser = (() => ({
isLoaded: true,
isSignedIn: true,
user: {
id: DEV_USER_ID,
fullName: "Dev User",
firstName: "Dev",
lastName: "User",
primaryEmailAddress: { emailAddress: "dev@localhost" },
imageUrl: "",
},
})) as unknown as typeof clerkUseUser;
Comment on lines +37 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

devUseUser stub is missing Clerk User instance methods — crashes in princeton-connect-row.tsx under dev auth.

princeton-connect-row.tsx was updated by this PR to import useUser from @/lib/auth, so in VITE_ENABLE_DEV_AUTH=true mode it receives the devUseUser stub. The component calls three methods on the returned user object that the stub does not implement:

  • user.createEmailAddress({ email })line 41 — called via the reverification wrapper when the user clicks "Send Code"
  • user.reload()lines 56 and 91 — called after email creation and after verification
  • user.emailAddresseslines 58 and 83 — iterated to find the newly created address

The as unknown as typeof clerkUseUser cast silences TypeScript, so the gap isn't caught at compile time. At runtime, clicking the Princeton connect button throws TypeError: user.createEmailAddress is not a function.

Fix: Add no-op stubs for the missing methods in devUseUser. For example:

user: {
  id: DEV_USER_ID,
  fullName: "Dev User",
  firstName: "Dev",
  lastName: "User",
  primaryEmailAddress: { emailAddress: "dev@localhost" },
  imageUrl: "",
  emailAddresses: [],
  createEmailAddress: async () => { throw new Error("Not supported in dev mode"); },
  reload: async () => {},
},

(The Princeton email flow is inherently non-functional without real Clerk, so throwing on createEmailAddress with a clear message is more developer-friendly than a silent TypeError.)


const devUseClerk = (() => ({
signOut: async () => {},
openSignIn: () => {},
openUserProfile: () => {},
})) as unknown as typeof clerkUseClerk;

// useReverification wraps an action that may need step-up auth; in dev, pass through.
const devUseReverification = (<T>(fn: T) =>
fn) as unknown as typeof clerkUseReverification;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apps/web/src/components/sandbox-result.tsx was not migrated to import from this module. It still imports useAuth directly from @clerk/clerk-react:

import { useAuth } from "@clerk/clerk-react";
import {
AlertTriangle,

In dev mode ClerkProvider is not mounted (see __root.tsx fetchClerkAuth), so calling Clerk's useAuth() directly will throw a React context error if GitHubAuthRequiredError renders — e.g., when a sandbox run returns error_code === "github_auth_required". SandboxResult is rendered in both the basic chat and workspaces routes via ChatMessagesmessage-blocks.tsx.

The fix is a one-line import swap in sandbox-result.tsx:

Suggested change
import { useAuth } from "@/lib/auth";

export const useAuth = DEV_AUTH ? devUseAuth : clerkUseAuth;
export const useUser = DEV_AUTH ? devUseUser : clerkUseUser;
export const useClerk = DEV_AUTH ? devUseClerk : clerkUseClerk;
export const useReverification = DEV_AUTH
? devUseReverification
: clerkUseReverification;
2 changes: 1 addition & 1 deletion apps/web/src/lib/chat-stream-context.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useAuth } from "@clerk/tanstack-react-start";
import { useQueryClient } from "@tanstack/react-query";
import {
createContext,
Expand All @@ -12,6 +11,7 @@ import {
useState,
} from "react";
import toast from "react-hot-toast";
import { useAuth } from "@/lib/auth";
import {
type AgentPermissionRequest,
type AgentQuestionAction,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/use-agent-catalog.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useAuth } from "@clerk/tanstack-react-start";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useAuth } from "@/lib/auth";
import { env } from "../env";
import type { AgentMode } from "./agent-mode";

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/use-agent-session-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useAuth } from "@clerk/tanstack-react-start";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useMemo } from "react";
import { useAuth } from "@/lib/auth";
import {
type AgentCommand,
type AgentConfigOption,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/use-chat-stream.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useAuth, useUser } from "@clerk/tanstack-react-start";
import { useCallback, useRef, useState } from "react";
import { useAuth, useUser } from "@/lib/auth";
import { env } from "../env";
import {
type AgentMode,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/use-follow-stream.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useAuth } from "@clerk/tanstack-react-start";
import { useCallback, useEffect, useState } from "react";
import { useAuth } from "@/lib/auth";
import { env } from "../env";
import { agentStatusLabel } from "./agent-mode";
import type { ConvoStreamState, StreamPart } from "./use-chat-stream";
Expand Down
Loading
Loading