Skip to content

Migrate from better auth to workos for SSO integration - #7

Merged
AFAskar merged 12 commits into
devfrom
Feature/Frontend/WorkOS-Migration
Feb 4, 2026
Merged

Migrate from better auth to workos for SSO integration#7
AFAskar merged 12 commits into
devfrom
Feature/Frontend/WorkOS-Migration

Conversation

@AFAskar

@AFAskar AFAskar commented Feb 4, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added custom 404 error page with enhanced user experience
    • Integrated WorkOS authentication system
  • Updates

    • Migrated authentication from previous provider to WorkOS
    • Refreshed theme and styling with updated color palette and design tokens
    • Updated environment configuration for new authentication provider
  • Documentation

    • Added project governance and architectural guidelines

@coderabbitai

coderabbitai Bot commented Feb 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR migrates the authentication system from Better Auth to WorkOS AuthKit, updates database connectivity from Vercel Postgres to postgres-js, and refactors environment variables accordingly. The TanStack Start application is updated with new authentication flows, route structures, and middleware integration. Supporting infrastructure includes project governance documentation and OpenAPI tooling updates.

Changes

Cohort / File(s) Summary
Authentication Migration
packages/auth/src/index.ts, packages/auth/env.ts, packages/auth/package.json, packages/auth/script/auth-cli.ts, apps/tanstack-start/src/auth/client.ts, apps/tanstack-start/src/auth/server.ts
Replaced Better Auth with WorkOS AuthKit. Removed custom auth initialization, updated exported types to UserInfo/NoUserInfo, replaced environment variables (AUTH_SECRET → WorkOS config keys), and eliminated CLI script for schema generation.
Database Connection Refactor
packages/db/src/client.ts, packages/db/drizzle.config.ts, packages/db/package.json, docker-compose.yml
Switched from @vercel/postgres to postgres-js library, updated POSTGRES_URL connection string handling (port 6543 → 5432), simplified drizzle config, and updated docker-compose environment variables.
TanStack Start App Authentication Updates
apps/tanstack-start/src/component/auth-showcase.tsx, apps/tanstack-start/src/routes/__root.tsx, apps/tanstack-start/src/routes/api/auth.$.ts, apps/tanstack-start/src/routes/api/auth/callback.tsx, apps/tanstack-start/src/start.ts
Replaced auth client flow with WorkOS useAuth hook, added AuthKitProvider to root, removed generic auth splat route, created new OAuth callback route at /api/auth/callback, and integrated authkitMiddleware in TanStack start configuration.
TRPC & API Context
packages/api/src/trpc.ts, apps/tanstack-start/src/lib/trpc.ts, apps/tanstack-start/src/routes/api/trpc.$.ts
Updated TRPC context to accept UserInfo/NoUserInfo from getAuth() instead of session lookup, made handlers async to retrieve auth server-side, and removed authApi dependencies from context.
Configuration & Environment
example.env, apps/tanstack-start/src/env.ts
Updated environment variable examples to use VITE_ prefix, replaced Next.js/NextAuth vars with WorkOS configuration (WORKOS_CLIENT_ID, WORKOS_API_KEY, WORKOS_REDIRECT_URI, WORKOS_COOKIE_PASSWORD), and fixed authEnv import path.
Route Tree & Navigation
apps/tanstack-start/src/routeTree.gen.ts, apps/tanstack-start/src/component/NotFound.tsx
Updated route definitions from /api/auth/$ splat to /api/auth/callback, added custom NotFound (404) component with navigation support.
Project Documentation & Tooling
.agent/rules/project-rules.md, .github/.copilot-instructions.md, .gitignore, package.json
Added project governance guidelines, updated Copilot instructions for pnpm usage and OpenAPI tooling (@hey-api/openapi-ts), added generated SDK ignore rule, introduced ai-service:generate script.
Theme & UI
packages/ui/src/theme.tsx, tooling/tailwind/theme.css
Removed inline theme detection script from provider (moved to root route), restructured Tailwind CSS color tokens and radius scale with oklch values and alpha-aware notations.

Sequence Diagram

sequenceDiagram
    participant Client as TanStack Start Client
    participant Router as TanStack Router
    participant AuthKit as AuthKit Provider
    participant Middleware as Request Middleware
    participant Server as API Server
    participant WorkOS as WorkOS Service

    Client->>Router: Navigate / trigger auth action
    Router->>AuthKit: getSignInUrl() request
    AuthKit->>Server: Server function call
    Server->>WorkOS: Request sign-in URL
    WorkOS-->>Server: Return OAuth URL
    Server-->>AuthKit: Return URL
    AuthKit-->>Client: Redirect to WorkOS login
    
    Client->>WorkOS: User authenticates
    WorkOS-->>Client: Redirect to /api/auth/callback
    
    Client->>Router: Navigate to /api/auth/callback
    Router->>Middleware: authkitMiddleware processes request
    Middleware->>Server: handleCallbackRoute()
    Server->>WorkOS: Verify & exchange callback
    WorkOS-->>Server: Return session/user info
    Server->>Middleware: Set secure auth cookie
    Middleware-->>Client: Redirect to home
    
    Client->>Server: Subsequent API request
    Middleware->>Server: getAuth() retrieves session from cookie
    Server-->>Client: Return authenticated response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Initialized the FrontEnd #6: Directly modifies the same TanStack Start authentication files, routes, TRPC context, and packages/auth exports introduced in this migration, indicating coordinated authentication architecture changes.

Poem

🐰 Hops through code with WorkOS cheer,
Better Auth's goodbye, clearer auth is here!
Postgres pooling, postgres-js done,
OAuth callbacks and routes made one!
From env to routes, the refactor's complete—
A cleaner auth dance, security sweet!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the pull request: migrating authentication from better-auth to WorkOS for SSO integration, which is clearly reflected across the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch Feature/Frontend/WorkOS-Migration

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tooling/tailwind/theme.css (1)

1-161: ⚠️ Potential issue | 🟡 Minor

Address Prettier formatting issues flagged by CI.

The pipeline indicates code style issues found that should be fixed by running Prettier with --write.

Run pnpm format --write or the equivalent command to fix formatting before merging.

The Biome error about Tailwind-specific syntax is a false positive—@theme inline and @variant dark are valid Tailwind CSS v4 directives.

🤖 Fix all issues with AI agents
In @.agent/rules/project-rules.md:
- Line 67: Fix the typo in the rule sentence: replace the misspelled word
"seperate" with "separate" in the line that currently reads 'AI service
shouldn't have a seperate auth flow...' so it becomes 'AI service shouldn't have
a separate auth flow...'; update the .agent/rules/project-rules.md entry
containing that sentence.

In @.github/.copilot-instructions.md:
- Line 6: Update the bullet text "always use pnpm and pnpx for package
management" to correct grammar/capitalization by capitalizing the first word and
ending with a period: replace it with "Always use pnpm and pnpx for package
management." Apply the same capitalization and punctuation fix to the duplicate
occurrence mentioned (line 31).

In `@apps/tanstack-start/src/component/auth-showcase.tsx`:
- Around line 1-41: The component AuthShowcase must await and catch errors from
getSignInUrlFn() and signOut(): wrap the getSignInUrlFn() call inside an async
try/catch in the Sign in button onClick, and handle failures by logging and
showing a user-visible error (e.g., set an error state or display alert/toast);
likewise change the signOut() invocation to an async function that awaits
signOut() inside try/catch and surfaces errors to the user. Update the handlers
in AuthShowcase to use await, try/catch, and a simple UI feedback mechanism
(error state or toast) so both getSignInUrlFn and signOut errors are handled and
reported.

In `@apps/tanstack-start/src/component/NotFound.tsx`:
- Around line 1-6: The NotFound component currently calls console.log("NotFound
triggered for path:", location.pathname) on every render; remove that call or
gate it to only run in development (e.g., wrap it with a check like NODE_ENV ===
'development' or an isDev flag) so location.pathname is not logged in
production; update the NotFound function (which uses useLocation and the
location variable) to either delete the console.log line or conditionally
execute it only when in dev.

In `@package.json`:
- Line 25: The npm script "ai-service:generate" currently calls the
`@hey-api/openapi-ts` CLI without an output path, causing the command to fail;
update the "ai-service:generate" script to add the -o (or --output) flag
followed by the intended output directory (for example "src/generated" or
similar) so the CLI writes generated files to that location; ensure the script
string still uses the same dlx invocation and URL argument and only appends the
-o <output-dir> option.

In `@packages/api/package.json`:
- Line 32: Prettier failed due to formatting in packages/api/package.json after
adding the dependency entry "@types/node": "catalog:"; run the repository
formatter (pnpm run format:fix from the repo root) to fix formatting and commit
the updated package.json, and while doing so verify the dependency value for the
"@types/node" entry is the intended version/resolution (replace the placeholder
"catalog:" with the correct version or spec if needed) so the package.json
remains valid.
🧹 Nitpick comments (4)
docker-compose.yml (1)

23-23: Hardcoded credentials are acceptable for local development but add a cautionary comment.

The static analysis tool flagged the plaintext password in POSTGRES_URL. While hardcoded credentials in docker-compose for local development are common practice, consider adding a comment clarifying this file is for development only to prevent accidental use in production.

📝 Suggested documentation
+    # WARNING: Development only - do not use these credentials in production
     - POSTGRES_URL=postgresql://postgres:password@application_db:5432/app_db
packages/db/src/client.ts (1)

6-16: Consider lazy initialization for the database client.

The current implementation throws at module load time if POSTGRES_URL is missing and eagerly creates a connection. This means:

  1. Importing this module in any code path will fail if the env var is absent, even if the database isn't needed for that request.
  2. The connection is created immediately at import time.

For serverless environments or cases where not all code paths need the database, consider lazy initialization:

♻️ Optional: Lazy initialization pattern
-const connectionString = process.env.POSTGRES_URL;
-
-if (!connectionString) {
-  throw new Error("Missing POSTGRES_URL environment variable");
-}
-
-/**
- * Database client using postgres.js
- * Works with both local Docker containers and Neon (via standard connection string)
- */
-const client = postgres(connectionString);
-
-export const db = drizzle(client, {
+let _db: ReturnType<typeof drizzle> | null = null;
+
+function getDb() {
+  if (_db) return _db;
+  
+  const connectionString = process.env.POSTGRES_URL;
+  if (!connectionString) {
+    throw new Error("Missing POSTGRES_URL environment variable");
+  }
+  
+  const client = postgres(connectionString);
+  _db = drizzle(client, {
+    schema,
+    casing: "snake_case",
+  });
+  return _db;
+}
+
+/**
+ * Database client using postgres.js
+ * Works with both local Docker containers and Neon (via standard connection string)
+ */
+export const db = new Proxy({} as ReturnType<typeof drizzle>, {
+  get(_, prop) {
+    return Reflect.get(getDb(), prop);
+  },
+});

If eager initialization is intentional for this project's architecture (e.g., always needs DB), the current approach is fine—just be aware of the tradeoff.

apps/tanstack-start/src/routes/__root.tsx (1)

15-19: Inline theme script needs CSP/XSS verification.

dangerouslySetInnerHTML injects an inline script. If you enforce a strict CSP, this will be blocked unless a nonce/hash is provided. Also ensure the script content stays static (no user input) to avoid XSS risk. Consider adding a CSP nonce or moving the logic to an external script if CSP is enabled.

Also applies to: 53-56, 68-68

apps/tanstack-start/src/routeTree.gen.ts (1)

14-104: Ensure WorkOS callback URL matches /api/auth/callback.

The generated route tree now exposes /api/auth/callback across route maps and types. Please verify the WorkOS dashboard/environment configuration uses the same callback URL; mismatches will break SSO redirects. Also ensure this generated file is re-built when routes change.

- The AI service receives all required inputs (user context, metrics, documents).
- The AI service must not fetch user or metrics data on its own.
- Inputs should be explicit, minimal, and versionable.
- AI service shouldn't have a seperate auth flow it should instead use the AI_SERVICE_KEY env var to authenticate requests from the main app which will have that variable sent using a secure header. No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typo: "seperate" should be "separate".

📝 Proposed fix
-- AI service shouldn't have a seperate auth flow it should instead use the AI_SERVICE_KEY env var to authenticate requests from the main app which will have that variable sent using a secure header.
+- AI service shouldn't have a separate auth flow; it should instead use the AI_SERVICE_KEY env var to authenticate requests from the main app which will have that variable sent using a secure header.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- AI service shouldn't have a seperate auth flow it should instead use the AI_SERVICE_KEY env var to authenticate requests from the main app which will have that variable sent using a secure header.
- AI service shouldn't have a separate auth flow; it should instead use the AI_SERVICE_KEY env var to authenticate requests from the main app which will have that variable sent using a secure header.
🤖 Prompt for AI Agents
In @.agent/rules/project-rules.md at line 67, Fix the typo in the rule sentence:
replace the misspelled word "seperate" with "separate" in the line that
currently reads 'AI service shouldn't have a seperate auth flow...' so it
becomes 'AI service shouldn't have a separate auth flow...'; update the
.agent/rules/project-rules.md entry containing that sentence.

- This is a PoC: prioritize developer speed and clarity over abstraction.
- Treat all backends as APIs; do not couple services together.
- The AI service is stateless and must not call application APIs directly.
- always use pnpm and pnpx for package management

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix grammar/capitalization in the guidance bullets.
Minor doc polish: capitalize “Always” and make the second sentence a proper sentence.

✍️ Proposed edit
-- always use pnpm and pnpx for package management
+- Always use pnpm and pnpx for package management.

-- All reusable UI components must live in the `packages/ui` package. use them
+- All reusable UI components must live in the `packages/ui` package. Use them.

Also applies to: 31-31

🤖 Prompt for AI Agents
In @.github/.copilot-instructions.md at line 6, Update the bullet text "always
use pnpm and pnpx for package management" to correct grammar/capitalization by
capitalizing the first word and ending with a period: replace it with "Always
use pnpm and pnpx for package management." Apply the same capitalization and
punctuation fix to the duplicate occurrence mentioned (line 31).

Comment on lines +1 to +41
import { useAuth } from "@workos/authkit-tanstack-react-start/client";
import { getSignInUrl } from "@workos/authkit-tanstack-react-start";
import { createServerFn } from "@tanstack/react-start";

import { Button } from "@governance/ui/button";

import { authClient } from "~/auth/client";
const getSignInUrlFn = createServerFn({ method: "GET" }).handler(async () => {
return await getSignInUrl();
});

export function AuthShowcase() {
const { data: session } = authClient.useSession();
const navigate = useNavigate();
const { user, loading, signOut } = useAuth();

if (!session) {
if (loading) {
return <div className="text-muted-foreground">Loading...</div>;
}

if (!user) {
return (
<Button
size="lg"
onClick={async () => {
const res = await authClient.signIn.social({
provider: "discord",
callbackURL: "/",
});
if (!res.data?.url) {
throw new Error("No URL returned from signInSocial");
}
await navigate({ href: res.data.url, replace: true });
const signInUrl = await getSignInUrlFn();
window.location.href = signInUrl;
}}
>
Sign in with Discord
Sign in with WorkOS
</Button>
);
}

return (
<div className="flex flex-col items-center justify-center gap-4">
<p className="text-center text-2xl">
<span>Logged in as {session.user.name}</span>
<span>
Logged in as {user.firstName} {user.lastName}
</span>
</p>
<p className="text-muted-foreground">{user.email}</p>

<Button
size="lg"
onClick={async () => {
await authClient.signOut();
await navigate({ href: "/", replace: true });
}}
>
<Button size="lg" onClick={() => signOut()}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "auth-showcase.tsx" | head -20

Repository: AFAskar/Governance-Agent

Length of output: 121


🏁 Script executed:

git ls-files | grep -E "(auth-showcase|authkit)" | head -30

Repository: AFAskar/Governance-Agent

Length of output: 119


🏁 Script executed:

fd "auth-showcase" --type f

Repository: AFAskar/Governance-Agent

Length of output: 119


🏁 Script executed:

cat -n ./apps/tanstack-start/src/component/auth-showcase.tsx

Repository: AFAskar/Governance-Agent

Length of output: 1569


🏁 Script executed:

rg "@workos/authkit-tanstack-react-start" -t tsx -t ts --context 3 | head -80

Repository: AFAskar/Governance-Agent

Length of output: 95


🏁 Script executed:

rg "getSignInUrl|signOut" -t tsx -t ts -A 2 -B 2 | head -100

Repository: AFAskar/Governance-Agent

Length of output: 95


🏁 Script executed:

find . -type f -name "package.json" -path "*/tanstack-start/*" -o -name "package.json" -path "*/root" | head -5

Repository: AFAskar/Governance-Agent

Length of output: 102


🏁 Script executed:

cat ./apps/tanstack-start/package.json

Repository: AFAskar/Governance-Agent

Length of output: 2028


🏁 Script executed:

rg "getSignInUrl|signOut" --type-list | grep -i tsx

Repository: AFAskar/Governance-Agent

Length of output: 457


🏁 Script executed:

rg "getSignInUrl|signOut" -A 3 -B 2 | head -150

Repository: AFAskar/Governance-Agent

Length of output: 2627


🏁 Script executed:

rg "useAuth" --max-count 20 -B 3 -A 5

Repository: AFAskar/Governance-Agent

Length of output: 1273


🏁 Script executed:

rg "try.*catch" apps/tanstack-start/src -B 2 -A 4 | head -80

Repository: AFAskar/Governance-Agent

Length of output: 50


🏁 Script executed:

rg "getSignInUrlFn" -B 2 -A 5

Repository: AFAskar/Governance-Agent

Length of output: 1355


🏁 Script executed:

rg "onClick.*async" apps/tanstack-start/src -B 2 -A 8 | head -100

Repository: AFAskar/Governance-Agent

Length of output: 872


🏁 Script executed:

rg "\.catch\|try\s*{" apps/tanstack-start/src -B 3 -A 4

Repository: AFAskar/Governance-Agent

Length of output: 163


🏁 Script executed:

find apps/tanstack-start/src -type f -name "*.ts" -o -name "*.tsx" | head -20

Repository: AFAskar/Governance-Agent

Length of output: 558


🏁 Script executed:

rg "\.catch|catch\s*\(" apps/tanstack-start/src -B 2 -A 3

Repository: AFAskar/Governance-Agent

Length of output: 50


🏁 Script executed:

cat apps/tanstack-start/src/routes/index.tsx

Repository: AFAskar/Governance-Agent

Length of output: 6966


🏁 Script executed:

rg "useState" apps/tanstack-start/src --max-count 10 -B 2 -A 2

Repository: AFAskar/Governance-Agent

Length of output: 50


🏁 Script executed:

grep -r "use client" apps/tanstack-start/src/component --include="*.tsx"

Repository: AFAskar/Governance-Agent

Length of output: 50


🏁 Script executed:

rg "onError|try|catch" packages/auth/src --max-count 20 -B 2 -A 3

Repository: AFAskar/Governance-Agent

Length of output: 50


🏁 Script executed:

cat apps/tanstack-start/src/component/auth-showcase.tsx | head -50

Repository: AFAskar/Governance-Agent

Length of output: 1247


🏁 Script executed:

web_search
What does `@workos/authkit-tanstack-react-start` getSignInUrl() and signOut() return and can they throw errors?

Repository: AFAskar/Governance-Agent

Length of output: 313


🌐 Web query:

@workos/authkit-tanstack-react-start getSignInUrl signOut error handling

💡 Result:

Where errors can happen (and how to handle them)

1) getSignInUrl() (server-side only)

getSignInUrl() is meant to be called in route loaders / server functions / server handlers (not in beforeLoad or directly in client components). The canonical pattern is to generate the URL and throw a TanStack redirect. (github.com)

Error handling pattern (loader):

import { createFileRoute, redirect } from '@tanstack/react-router'
import { getSignInUrl } from '@workos/authkit-tanstack-react-start'

export const Route = createFileRoute('/signin')({
  loader: async () => {
    try {
      const url = await getSignInUrl({ data: { returnPathname: '/dashboard' } })
      throw redirect({ href: url })
    } catch (err) {
      // log + send user somewhere safe
      console.error('getSignInUrl failed', err)
      throw redirect({ href: '/auth-error' })
    }
  },
})

(Options like returnPathname are supported.) (github.com)

2) signOut() (server-side)

Server-side signOut() ends the session and redirects to WorkOS logout (optionally with a returnTo). (github.com)

Error handling pattern (loader):

import { createFileRoute, redirect } from '@tanstack/react-router'
import { signOut } from '@workos/authkit-tanstack-react-start'

export const Route = createFileRoute('/logout')({
  loader: async () => {
    try {
      await signOut({ data: { returnTo: '/goodbye' } })
    } catch (err) {
      console.error('signOut failed', err)
      throw redirect({ href: '/auth-error' })
    }
  },
})

Note: if your WorkOS Dashboard has neither a Sign-out redirect nor an App homepage URL configured, WorkOS will redirect users to an error page on logout. (github.com)

3) signOut() (client-side via useAuth())

Client-side sign-out is available via the useAuth() hook and returns a promise, so handle it with try/catch (or .catch). (github.com)

import { useAuth } from '@workos/authkit-tanstack-react-start/client'

function SignOutButton() {
  const { signOut } = useAuth()

  return (
    <button
      onClick={async () => {
        try {
          await signOut({ returnTo: '/goodbye' })
        } catch (err) {
          console.error('Client signOut failed', err)
          window.location.href = '/auth-error'
        }
      }}
    >
      Sign out
    </button>
  )
}

4) OAuth callback failures (best place for “auth flow” error handling)

If you want centralized handling for login/callback errors, use handleCallbackRoute({ onError }) to return your own Response (JSON, redirect, etc.). (github.com)

import { handleCallbackRoute } from '@workos/authkit-tanstack-react-start'

GET: handleCallbackRoute({
  onError: ({ error }) => {
    console.error('Auth callback failed:', error)
    return new Response('Authentication failed', { status: 500 })
  },
})

Citations:


Add error handling for signOut() and getSignInUrl() to improve UX.

The signOut() hook method returns a promise that is not being awaited or caught (line 41). Similarly, getSignInUrlFn() can fail without user feedback (line 23). Per the library's documented patterns, both operations should include try/catch with user-visible error messaging.

Suggested fix
 import { useAuth } from "@workos/authkit-tanstack-react-start/client";
 import { getSignInUrl } from "@workos/authkit-tanstack-react-start";
 import { createServerFn } from "@tanstack/react-start";
+import { useState } from "react";

 import { Button } from "@governance/ui/button";

 const getSignInUrlFn = createServerFn({ method: "GET" }).handler(async () => {
   return await getSignInUrl();
 });

 export function AuthShowcase() {
   const { user, loading, signOut } = useAuth();
+  const [error, setError] = useState<string | null>(null);

   if (loading) {
     return <div className="text-muted-foreground">Loading...</div>;
   }

   if (!user) {
     return (
-      <Button
-        size="lg"
-        onClick={async () => {
-          const signInUrl = await getSignInUrlFn();
-          window.location.href = signInUrl;
-        }}
-      >
-        Sign in with WorkOS
-      </Button>
+      <div className="flex flex-col items-center gap-2">
+        {error && <p className="text-destructive">{error}</p>}
+        <Button
+          size="lg"
+          onClick={async () => {
+            try {
+              setError(null);
+              const signInUrl = await getSignInUrlFn();
+              window.location.href = signInUrl;
+            } catch (err) {
+              console.error(err);
+              setError("Unable to start sign-in. Please try again.");
+            }
+          }}
+        >
+          Sign in with WorkOS
+        </Button>
+      </div>
     );
   }

   return (
     <div className="flex flex-col items-center justify-center gap-4">
+      {error && <p className="text-destructive">{error}</p>}
       <p className="text-center text-2xl">
         <span>
           Logged in as {user.firstName} {user.lastName}
         </span>
       </p>
       <p className="text-muted-foreground">{user.email}</p>

-      <Button size="lg" onClick={() => signOut()}>
+      <Button
+        size="lg"
+        onClick={async () => {
+          try {
+            setError(null);
+            await signOut();
+          } catch (err) {
+            console.error(err);
+            setError("Unable to sign out. Please try again.");
+          }
+        }}
+      >
         Sign out
       </Button>
     </div>
   );
 }
🤖 Prompt for AI Agents
In `@apps/tanstack-start/src/component/auth-showcase.tsx` around lines 1 - 41, The
component AuthShowcase must await and catch errors from getSignInUrlFn() and
signOut(): wrap the getSignInUrlFn() call inside an async try/catch in the Sign
in button onClick, and handle failures by logging and showing a user-visible
error (e.g., set an error state or display alert/toast); likewise change the
signOut() invocation to an async function that awaits signOut() inside try/catch
and surfaces errors to the user. Update the handlers in AuthShowcase to use
await, try/catch, and a simple UI feedback mechanism (error state or toast) so
both getSignInUrlFn and signOut errors are handled and reported.

Comment on lines +1 to +6
import { Link, useLocation } from "@tanstack/react-router";

export function NotFound() {
const location = useLocation();
console.log("NotFound triggered for path:", location.pathname);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove or gate the render-time console.log.

Logging on every 404 render can spam production logs and expose path data. Consider gating to dev or removing.

🛠️ Suggested fix (gate to dev)
 export function NotFound() {
-    const location = useLocation();
-    console.log("NotFound triggered for path:", location.pathname);
+    const location = useLocation();
+    if (import.meta.env.DEV) {
+        console.log("NotFound triggered for path:", location.pathname);
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Link, useLocation } from "@tanstack/react-router";
export function NotFound() {
const location = useLocation();
console.log("NotFound triggered for path:", location.pathname);
import { Link, useLocation } from "@tanstack/react-router";
export function NotFound() {
const location = useLocation();
if (import.meta.env.DEV) {
console.log("NotFound triggered for path:", location.pathname);
}
🤖 Prompt for AI Agents
In `@apps/tanstack-start/src/component/NotFound.tsx` around lines 1 - 6, The
NotFound component currently calls console.log("NotFound triggered for path:",
location.pathname) on every render; remove that call or gate it to only run in
development (e.g., wrap it with a check like NODE_ENV === 'development' or an
isDev flag) so location.pathname is not logged in production; update the
NotFound function (which uses useLocation and the location variable) to either
delete the console.log line or conditionally execute it only when in dev.

Comment thread package.json
"typecheck": "turbo run typecheck",
"ui-add": "turbo run ui-add"
"ui-add": "turbo run ui-add",
"ai-service:generate": "pnpm dlx @hey-api/openapi-ts -i http://localhost:8000/openapi.json"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find and read package.json to confirm the script
cat -n package.json

Repository: AFAskar/Governance-Agent

Length of output: 1706


🏁 Script executed:

# Check for any openapi-ts or hey-api configuration files
fd -e "config.ts" -e "config.js" -e "config.json" | grep -i "openapi\|hey-api"
fd -i "openapi-ts.config" -o "openapi.config"

Repository: AFAskar/Governance-Agent

Length of output: 215


🌐 Web query:

@hey-api/openapi-ts output directory -o flag requirement documentation

💡 Result:

@hey-api/openapi-ts requires you to set an output location (the folder where generated files are written).

  • CLI: use the -o flag to set the output directory (commonly shown with -i for input), e.g. npx @hey-api/openapi-ts -i … -o src/client. [1]
  • Config file / programmatic API: you must set output (either a string path or an object with path), e.g. output: 'src/client' or output: { path: 'src/client', … }. The docs explicitly state: “You must set the output so we know where to generate your files.” [2]

Sources:
[1] Hey API “Get Started” (CLI quick start showing -o) [1]
[2] Hey API “Configuration → Output” (output is required) [2]


🏁 Script executed:

# Search for openapi-ts config files
fd "\.openapi-ts\.config" -type f
fd "openapi-ts\.config" -type f
fd "openapi\.config" -type f

# Also check common config locations
find . -name "*openapi*" -type f 2>/dev/null | head -20

Repository: AFAskar/Governance-Agent

Length of output: 580


🏁 Script executed:

# Check if there's a config file in packages/api or similar
find . -path "*/packages/*/openapi*" -o -path "*/packages/api/*config*" 2>/dev/null | head -20

Repository: AFAskar/Governance-Agent

Length of output: 128


🏁 Script executed:

# Simpler search for any openapi files
rg "openapi" --type-list | head -5
rg "openapi" -i -l | head -20

Repository: AFAskar/Governance-Agent

Length of output: 532


🏁 Script executed:

# Check the README.md for any config info
cat services/ai-service/src/api/models/README.md

Repository: AFAskar/Governance-Agent

Length of output: 372


Add output directory to the ai-service:generate script.

The @hey-api/openapi-ts CLI requires the -o flag to specify where generated files should be written. Without it, the command will fail.

Suggested fix
-    "ai-service:generate": "pnpm dlx `@hey-api/openapi-ts` -i http://localhost:8000/openapi.json"
+    "ai-service:generate": "pnpm dlx `@hey-api/openapi-ts` -i http://localhost:8000/openapi.json -o packages/api/src/generated"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"ai-service:generate": "pnpm dlx @hey-api/openapi-ts -i http://localhost:8000/openapi.json"
"ai-service:generate": "pnpm dlx `@hey-api/openapi-ts` -i http://localhost:8000/openapi.json -o packages/api/src/generated"
🤖 Prompt for AI Agents
In `@package.json` at line 25, The npm script "ai-service:generate" currently
calls the `@hey-api/openapi-ts` CLI without an output path, causing the command to
fail; update the "ai-service:generate" script to add the -o (or --output) flag
followed by the intended output directory (for example "src/generated" or
similar) so the CLI writes generated files to that location; ensure the script
string still uses the same dlx invocation and URL argument and only appends the
-o <output-dir> option.

Comment thread packages/api/package.json
"@governance/eslint-config": "workspace:*",
"@governance/prettier-config": "workspace:*",
"@governance/tsconfig": "workspace:*",
"@types/node": "catalog:",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Address the Prettier formatting failure.

The CI pipeline reports a Prettier formatting check failure in this package. Run pnpm run format:fix from the root to resolve the formatting issues.

The addition of @types/node is appropriate for Node.js type definitions.

🤖 Prompt for AI Agents
In `@packages/api/package.json` at line 32, Prettier failed due to formatting in
packages/api/package.json after adding the dependency entry "@types/node":
"catalog:"; run the repository formatter (pnpm run format:fix from the repo
root) to fix formatting and commit the updated package.json, and while doing so
verify the dependency value for the "@types/node" entry is the intended
version/resolution (replace the placeholder "catalog:" with the correct version
or spec if needed) so the package.json remains valid.

@AFAskar
AFAskar merged commit 8b24df1 into dev Feb 4, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant