Repository: stack-auth/stack-auth
Pull request #611: Init script analytics
Author: login: N2D4
Created: 2025-04-07T18:35:14Z | Merged: 2025-04-08T23:34:47Z


<!-- ELLIPSIS_HIDDEN -->


> [!IMPORTANT]
> Integrates PostHog analytics into the dashboard and init-stack package to track setup events and errors.
> 
>   - **Analytics Integration**:
>     - Adds `posthog-js` to `apps/dashboard/package.json` and `posthog-node` to `packages/init-stack/package.json`.
>     - New `PostHog` component in `wizard-congrats/page.tsx` to capture and merge distinct IDs.
>     - Captures events like `start`, `project-type-selected`, `package-manager-selected`, `dependencies-installed`, `complete`, and `error` in `index.ts`.
>   - **Behavior**:
>     - Updates `wizard-congrats/page.tsx` to include `PostHog` component for analytics.
>     - In `index.ts`, captures analytics events during the setup process, including error handling.
>   - **Misc**:
>     - Updates `posthog-js` version in `apps/dashboard/package.json`.
>     - Adds `crypto` and `os` imports in `index.ts` for generating distinct IDs and capturing system info.
> 
> <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral)<sup> for 229b3e315906b401f8e5a286c66be1f60644453a. It will automatically update as commits are pushed.</sup>


<!-- ELLIPSIS_HIDDEN -->


Diff:
diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json
index 4fee7da84a..349c6745e0 100644
--- a/apps/dashboard/package.json
+++ b/apps/dashboard/package.json
@@ -36,7 +36,7 @@
     "lucide-react": "^0.378.0",
     "next": "15.2.3",
     "next-themes": "^0.2.1",
-    "posthog-js": "^1.149.1",
+    "posthog-js": "^1.234.9",
     "react": "19.0.0",
     "react-dom": "19.0.0",
     "react-globe.gl": "^2.28.2",
diff --git a/apps/dashboard/src/app/(main)/wizard-congrats/page.tsx b/apps/dashboard/src/app/(main)/wizard-congrats/page.tsx
index 5bfb75fd92..ea2b7ed9e0 100644
--- a/apps/dashboard/src/app/(main)/wizard-congrats/page.tsx
+++ b/apps/dashboard/src/app/(main)/wizard-congrats/page.tsx
@@ -1,6 +1,7 @@
 import { Confetti } from "@/components/confetti";
 import { Card, CardContent, CardFooter, CardHeader, InlineCode, Typography } from "@stackframe/stack-ui";
 import Actions from "./actions";
+import PostHog from "./posthog";
 
 export const metadata = {
   title: "Setup complete!",
@@ -9,6 +10,7 @@ export const metadata = {
 export default function WizardCongratsPage() {
   return (
     <>
+      <PostHog />
       <Confetti />
       <style>
         {`
diff --git a/apps/dashboard/src/app/(main)/wizard-congrats/posthog.tsx b/apps/dashboard/src/app/(main)/wizard-congrats/posthog.tsx
new file mode 100644
index 0000000000..797ce73ea3
--- /dev/null
+++ b/apps/dashboard/src/app/(main)/wizard-congrats/posthog.tsx
@@ -0,0 +1,32 @@
+"use client";
+
+import { useRouter } from "@/components/router";
+import { useSearchParams } from "next/navigation";
+import { usePostHog } from "posthog-js/react";
+import { useEffect } from "react";
+
+export default function PostHog() {
+  const posthog = usePostHog();
+  const searchParams = useSearchParams();
+  const router = useRouter();
+  useEffect(() => {
+    const distinctId = searchParams.get("stack-init-id");
+    if (distinctId) {
+      posthog.capture('$merge_dangerously',
+        {
+          alias: distinctId,
+        });
+      const newSearchParams = new URLSearchParams();
+      searchParams.forEach((value, key) => {
+        if (key !== "stack-init-id") {
+          newSearchParams.append(key, value);
+        }
+      });
+      const newUrl = window.location.pathname +
+        (newSearchParams.toString() ? `?${newSearchParams.toString()}` : '');
+      router.replace(newUrl);
+    }
+  }, [posthog, searchParams, router]);
+
+  return null;
+}
diff --git a/packages/init-stack/package.json b/packages/init-stack/package.json
index c4516fdd9d..2d20a96cbe 100644
--- a/packages/init-stack/package.json
+++ b/packages/init-stack/package.json
@@ -40,7 +40,8 @@
     "@stackframe/stack-shared": "workspace:*",
     "commander": "^13.1.0",
     "inquirer": "^9.2.19",
-    "open": "^10.1.0"
+    "open": "^10.1.0",
+    "posthog-node": "^4.1.0"
   },
   "devDependencies": {
     "@types/inquirer": "^9.0.7",
diff --git a/packages/init-stack/src/index.ts b/packages/init-stack/src/index.ts
index 2b6d2318b1..6cebe42a3f 100644
--- a/packages/init-stack/src/index.ts
+++ b/packages/init-stack/src/index.ts
@@ -1,9 +1,12 @@
 import * as child_process from "child_process";
 import { Command } from "commander";
+import * as crypto from 'crypto';
 import * as fs from "fs";
 import inquirer from "inquirer";
 import open from "open";
+import * as os from 'os';
 import * as path from "path";
+import { PostHog } from 'posthog-node';
 import packageJson from '../package.json';
 
 const jsLikeFileExtensions: string[] = [
@@ -113,6 +116,26 @@ const nextSteps: string[] = [
   `Create an account and Stack Auth API key for your project on https://app.stack-auth.com`,
 ];
 
+
+const STACK_AUTH_PUBLIC_HOG_KEY = "phc_vIUFi0HzHo7oV26OsaZbUASqxvs8qOmap1UBYAutU4k";
+const EVENT_PREFIX = "stack-init-";
+const ph_client = new PostHog(STACK_AUTH_PUBLIC_HOG_KEY, {
+  host: "https://eu.i.posthog.com",
+  flushAt: 1,
+  flushInterval: 0,
+});
+const distinctId = crypto.randomUUID();
+
+
+async function capture(event: string, properties: Record<string, any>) {
+  ph_client.capture({
+    event: `${EVENT_PREFIX}${event}`,
+    distinctId,
+    properties,
+  });
+}
+
+
 async function main(): Promise<void> {
   // Welcome message
   console.log();
@@ -132,6 +155,19 @@ async function main(): Promise<void> {
   `);
   console.log();
 
+  await capture("start", {
+    version: packageJson.version,
+    isDryRun,
+    isNeon,
+    typeFromArgs,
+    packageManagerFromArgs,
+    isClient,
+    isServer,
+    noBrowser,
+    platform: os.platform(),
+    arch: os.arch(),
+    nodeVersion: process.version,
+  });
 
   // Wait just briefly so we can use `Steps` in here (it's defined only after the call to `main()`)
   await new Promise<void>((resolve) => resolve());
@@ -143,8 +179,13 @@ async function main(): Promise<void> {
 
 
   // Steps
-  const { packageJson } = await Steps.getProject();
-  const type = await Steps.getProjectType({ packageJson });
+  const { packageJson: projectPackageJson } = await Steps.getProject();
+  const type = await Steps.getProjectType({ packageJson: projectPackageJson });
+
+  await capture("project-type-selected", {
+    type,
+    wasSpecifiedInArgs: !!typeFromArgs,
+  });
 
   await Steps.addStackPackage(type);
   if (isNeon) packagesToInstall.push('@neondatabase/serverless');
@@ -152,7 +193,7 @@ async function main(): Promise<void> {
   await Steps.writeEnvVars(type);
 
   if (type === "next") {
-    const projectInfo = await Steps.getNextProjectInfo({ packageJson });
+    const projectInfo = await Steps.getNextProjectInfo({ packageJson: projectPackageJson });
     await Steps.updateNextLayoutFile(projectInfo);
     await Steps.writeStackAppFile(projectInfo, "server");
     await Steps.writeNextHandlerFile(projectInfo);
@@ -181,6 +222,12 @@ async function main(): Promise<void> {
   }
 
   const { packageManager } = await Steps.getPackageManager();
+
+  await capture(`package-manager-selected`, {
+    packageManager,
+    wasSpecifiedInArgs: !!packageManagerFromArgs,
+  });
+
   await Steps.ensureReady(type);
 
 
@@ -193,6 +240,10 @@ async function main(): Promise<void> {
     cwd: projectPath,
   });
 
+  await capture(`dependencies-installed`, {
+    packageManager,
+    packages: packagesToInstall,
+  });
 
   // Write files
   console.log();
@@ -220,6 +271,18 @@ async function main(): Promise<void> {
   }
   console.log();
 
+  await capture("complete", {
+    success: true,
+    type,
+    packageManager,
+    isNeon,
+    isClient,
+    isServer,
+    noBrowser,
+    filesCreated,
+    filesModified,
+    commandsExecuted,
+  });
 
   // Success!
   console.log(`
@@ -230,8 +293,8 @@ ${colorize.green`Successfully installed Stack! 🚀🚀🚀`}
 ${colorize.bold`Next steps:`}
 
 1. ${noBrowser ?
-    `Create a project at https://app.stack-auth.com and get your API keys` :
-    `Complete the setup in your browser to get your API keys`}
+      `Create a project at https://app.stack-auth.com and get your API keys` :
+      `Complete the setup in your browser to get your API keys`}
 2. Add the API keys to your .env.local file
 3. Import the Stack components in your app
 4. Add authentication to your app
@@ -239,12 +302,20 @@ ${colorize.bold`Next steps:`}
 For more information, please visit https://docs.stack-auth.com/getting-started/setup
   `.trim());
   if (!process.env.STACK_DISABLE_INTERACTIVE && !noBrowser) {
-    await open("https://app.stack-auth.com/wizard-congrats");
+    await open(`https://app.stack-auth.com/wizard-congrats?stack-init-id=${encodeURIComponent(distinctId)}`);
   }
+  await ph_client.shutdown();
 }
 
 main()
-  .catch((err) => {
+  .catch(async (err) => {
+    try {
+      await capture("error", {
+        error: err.message,
+        errorType: err instanceof UserError ? "UserError" : "SystemError",
+        stack: err.stack,
+      });
+    } catch (e) { }
     if (!(err instanceof UserError)) {
       console.error(err);
     }
@@ -267,6 +338,7 @@ main()
       console.error(`Error message: ${err.message}`);
     }
     console.error();
+    await ph_client.shutdown();
     process.exit(1);
   });
 
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f2a306a093..a144c0535e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -323,8 +323,8 @@ importers:
         specifier: ^0.2.1
         version: 0.2.1(next@15.2.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
       posthog-js:
-        specifier: ^1.149.1
-        version: 1.149.2
+        specifier: ^1.234.9
+        version: 1.234.9
       react:
         specifier: 19.0.0
         version: 19.0.0
@@ -844,6 +844,9 @@ importers:
       open:
         specifier: ^10.1.0
         version: 10.1.0
+      posthog-node:
+        specifier: ^4.1.0
+        version: 4.1.0
     devDependencies:
       '@types/inquirer':
         specifier: ^9.0.7
@@ -7197,6 +7200,9 @@ packages:
   core-js-compat@3.40.0:
     resolution: {integrity: sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==}
 
+  core-js@3.41.0:
+    resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==}
+
   core-util-is@1.0.3:
     resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
 
@@ -10123,8 +10129,16 @@ packages:
     resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
     engines: {node: '>=0.10.0'}
 
-  posthog-js@1.149.2:
-    resolution: {integrity: sha512-4tNtVJkq3wZ5CvfOEp3Jtl/r3ogZb5To+bdu7JoO5QjkpTY9TV1pfo/Ag4keODpAzRDahC8OaCoIr4mY3dSK4g==}
+  posthog-js@1.234.9:
+    resolution: {integrity: sha512-Qxpg9YOlLa59lbkYcONZ9efmB6KTgkePnkhCTSnKIyUd826cs4J6VDTKHu+V/lugRtWLWB9R7CckvFE8QPaTbg==}
+    peerDependencies:
+      '@rrweb/types': 2.0.0-alpha.17
+      rrweb-snapshot: 2.0.0-alpha.17
+    peerDependenciesMeta:
+      '@rrweb/types':
+        optional: true
+      rrweb-snapshot:
+        optional: true
 
   posthog-node@4.1.0:
     resolution: {integrity: sha512-Fd+aMWLjUttlPrfOniDWs35v62rOEIqP5GBzUvRswsNY8rr1g1KuDobqaRFGMCNnrtDmhzUN8y7QucrcwMY/+w==}
@@ -11824,8 +11838,8 @@ packages:
   wcwidth@1.0.1:
     resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
 
-  web-vitals@4.2.2:
-    resolution: {integrity: sha512-nYfoOqb4EmElljyXU2qdeE76KsvoHdftQKY4DzA9Aw8DervCg2bG634pHLrJ/d6+B4mE3nWTSJv8Mo7B2mbZkw==}
+  web-vitals@4.2.4:
+    resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
 
   webidl-conversions@3.0.1:
     resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
@@ -17930,6 +17944,8 @@ snapshots:
     dependencies:
       browserslist: 4.24.4
 
+  core-js@3.41.0: {}
+
   core-util-is@1.0.3: {}
 
   cors@2.8.5:
@@ -21583,15 +21599,16 @@ snapshots:
     dependencies:
       xtend: 4.0.2
 
-  posthog-js@1.149.2:
+  posthog-js@1.234.9:
     dependencies:
+      core-js: 3.41.0
       fflate: 0.4.8
       preact: 10.22.0
-      web-vitals: 4.2.2
+      web-vitals: 4.2.4
 
   posthog-node@4.1.0:
     dependencies:
-      axios: 1.7.4
+      axios: 1.7.7
       rusha: 0.8.14
     transitivePeerDependencies:
       - debug
@@ -23924,7 +23941,7 @@ snapshots:
     dependencies:
       defaults: 1.0.4
 
-  web-vitals@4.2.2: {}
+  web-vitals@4.2.4: {}
 
   webidl-conversions@3.0.1: {}
 


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 3 out of 6 changed files in this pull request and generated no comments.

<details>
<summary>Files not reviewed (3)</summary>

* **apps/dashboard/package.json**: Language not supported
* **packages/init-stack/package.json**: Language not supported
* **pnpm-lock.yaml**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**packages/init-stack/src/index.ts:317**
* Ensure that 'UserError' is imported or defined in this file to prevent a runtime ReferenceError when handling errors.
```
errorType: err instanceof UserError ? "UserError" : "SystemError",
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T18:35:39Z
user: login: recurseml[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-07T18:36:14Z
user: login: ellipsis-dev[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T18:39:09Z
user: login: N2D4
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T18:47:48Z
user: login: ellipsis-dev[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T18:51:18Z
user: login: fomalhautb
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T21:22:46Z

Review comments:
diff_hunk: @@ -9,6 +10,7 @@ export const metadata = {
 export default function WizardCongratsPage() {
   return (
     <>
+      <PostHog />
path: apps/dashboard/src/app/(main)/wizard-congrats/page.tsx
user: login: recurseml[bot]
body: The PostHog component is using the 'use client' directive and contains client-side hooks (useEffect, usePostHog, useSearchParams, useRouter), but it's being rendered directly in a page.tsx file which by Next.js 13+ conventions is a Server Component by default. This will cause runtime errors because client-side hooks cannot be used in Server Components. The component should either be moved to a separate client component wrapper or the page itself should be marked as a client component with 'use client'.

📚 [Relevant Docs](https://nextjs.org/docs/getting-started/react-essentials#when-to-use-server-and-client-components)

---

> *React with 👍 to tell me that this comment was useful, or 👎 if not (and I'll stop posting more comments like this in the future)*
created_at: 2025-04-07T18:36:14Z
author_association: NONE
diff_hunk: @@ -230,21 +295,29 @@ ${colorize.green`Successfully installed Stack! 🚀🚀🚀`}
 ${colorize.bold`Next steps:`}
 
 1. ${noBrowser ?
-    `Create a project at https://app.stack-auth.com and get your API keys` :
-    `Complete the setup in your browser to get your API keys`}
+      `Create a project at https://app.stack-auth.com and get your API keys` :
+      `Complete the setup in your browser to get your API keys`}
 2. Add the API keys to your .env.local file
 3. Import the Stack components in your app
 4. Add authentication to your app
 
 For more information, please visit https://docs.stack-auth.com/getting-started/setup
   `.trim());
   if (!process.env.STACK_DISABLE_INTERACTIVE && !noBrowser) {
-    await open("https://app.stack-auth.com/wizard-congrats");
+    await open(`https://app.stack-auth.com/wizard-congrats?stack-init-id=${distinctId}`);
path: packages/init-stack/src/index.ts
user: login: ellipsis-dev[bot]
body: Consider using a tagged template literal (e.g. `urlString```) for constructing the URL (`https://app.stack-auth.com/wizard-congrats?stack-init-id=${distinctId}`) to align with our URL handling guidelines. This minimizes any potential issues with URL fragments in the future.
created_at: 2025-04-07T18:39:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,6 +222,13 @@ async function main(): Promise<void> {
   }
 
   const { packageManager } = await Steps.getPackageManager();
+
+  // Track package manager selection
path: packages/init-stack/src/index.ts
user: login: N2D4
body: ```suggestion
```
created_at: 2025-04-07T18:45:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -193,6 +241,11 @@ async function main(): Promise<void> {
     cwd: projectPath,
   });
 
+  // Track dependencies installation
path: packages/init-stack/src/index.ts
user: login: N2D4
body: ```suggestion
```
created_at: 2025-04-07T18:46:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -230,21 +295,29 @@ ${colorize.green`Successfully installed Stack! 🚀🚀🚀`}
 ${colorize.bold`Next steps:`}
 
 1. ${noBrowser ?
-    `Create a project at https://app.stack-auth.com and get your API keys` :
-    `Complete the setup in your browser to get your API keys`}
+      `Create a project at https://app.stack-auth.com and get your API keys` :
+      `Complete the setup in your browser to get your API keys`}
 2. Add the API keys to your .env.local file
 3. Import the Stack components in your app
 4. Add authentication to your app
 
 For more information, please visit https://docs.stack-auth.com/getting-started/setup
   `.trim());
   if (!process.env.STACK_DISABLE_INTERACTIVE && !noBrowser) {
-    await open("https://app.stack-auth.com/wizard-congrats");
+    await open(`https://app.stack-auth.com/wizard-congrats?stack-init-id=${distinctId}`);
path: packages/init-stack/src/index.ts
user: login: N2D4
body: good bot
created_at: 2025-04-07T18:46:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -230,21 +295,29 @@ ${colorize.green`Successfully installed Stack! 🚀🚀🚀`}
 ${colorize.bold`Next steps:`}
 
 1. ${noBrowser ?
-    `Create a project at https://app.stack-auth.com and get your API keys` :
-    `Complete the setup in your browser to get your API keys`}
+      `Create a project at https://app.stack-auth.com and get your API keys` :
+      `Complete the setup in your browser to get your API keys`}
 2. Add the API keys to your .env.local file
 3. Import the Stack components in your app
 4. Add authentication to your app
 
 For more information, please visit https://docs.stack-auth.com/getting-started/setup
   `.trim());
   if (!process.env.STACK_DISABLE_INTERACTIVE && !noBrowser) {
-    await open("https://app.stack-auth.com/wizard-congrats");
+    await open(`https://app.stack-auth.com/wizard-congrats?stack-init-id=${distinctId}`);
path: packages/init-stack/src/index.ts
user: login: ellipsis-dev[bot]
body: Consider using a tagged template literal (e.g. `urlString```) or applying `encodeURIComponent` to URL parameters when constructing URLs. This complies with our URL handling best practices.
```suggestion
    await open(`https://app.stack-auth.com/wizard-congrats?stack-init-id=${encodeURIComponent(distinctId)}`);
```
created_at: 2025-04-07T18:51:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,6 +116,26 @@
   `Create an account and Stack Auth API key for your project on https://app.stack-auth.com`,
 ];
 
+
+const STACK_AUTH_PUBLIC_HOG_KEY = "phc_vIUFi0HzHo7oV26OsaZbUASqxvs8qOmap1UBYAutU4k";
+const EVENT_PREFIX = "stack-init-";
+const ph_client = new PostHog(STACK_AUTH_PUBLIC_HOG_KEY, {
+  host: "https://eu.i.posthog.com",
+  flushAt: 1,
+  flushInterval: 0,
+});
+const distinctId = crypto.randomUUID();
+
+
+async function capture(event: string, properties: Record<string, any>) {
+  ph_client.capture({
path: packages/init-stack/src/index.ts
user: login: ellipsis-dev[bot]
body: Consider awaiting the `ph_client.capture` call within the capture function (if it returns a promise) to ensure events are sent exactly when expected before shutting down the client.
```suggestion
  await ph_client.capture({
```
created_at: 2025-04-07T18:51:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-04-07T18:35:19Z
body: [vc]: #ZUMzqs8LCUFAYr3X138BV+ZCzVp6atV2fbKAoASCyQY=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJzdGFjay1iYWNrZW5kIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvYmFja2VuZCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJzdGFjay1iYWNrZW5kLWdpdC1pbml0LXN0YWNrLWFuYWx5dGljcy1teS10ZWFtLTIwYTc5NTFmLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL215LXRlYW0tMjBhNzk1MWYvc3RhY2stYmFja2VuZC9IamtuVGtRVnlFTExzTU5xeTV5cmdKZDRSTlZrIiwicHJldmlld1VybCI6InN0YWNrLWJhY2tlbmQtZ2l0LWluaXQtc3RhY2stYW5hbHl0aWNzLW15LXRlYW0tMjBhNzk1MWYudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCJ9LHsibmFtZSI6InN0YWNrLWRlbW8iLCJyb290RGlyZWN0b3J5IjoiZXhhbXBsZXMvZGVtbyIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJzdGFjay1kZW1vLWdpdC1pbml0LXN0YWNrLWFuYWx5dGljcy1teS10ZWFtLTIwYTc5NTFmLnZlcmNlbC5hcHAifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL215LXRlYW0tMjBhNzk1MWYvc3RhY2stZGVtby9BMXo0RXVETEN5ZG9YMXZFbkZFR2F0WTU5QWRzIiwicHJldmlld1VybCI6InN0YWNrLWRlbW8tZ2l0LWluaXQtc3RhY2stYW5hbHl0aWNzLW15LXRlYW0tMjBhNzk1MWYudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCJ9LHsibmFtZSI6InN0YWNrLWRhc2hib2FyZCIsInJvb3REaXJlY3RvcnkiOiJhcHBzL2Rhc2hib2FyZCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJzdGFjay1kYXNoYm9hcmQtZ2l0LWluaXQtc3RhY2stYW5hbHl0aWNzLW15LXRlYW0tMjBhNzk1MWYudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vbXktdGVhbS0yMGE3OTUxZi9zdGFjay1kYXNoYm9hcmQvR2o4cEFSYmNOS1ZCa29VY01WVlBTdEZ4UGZzbiIsInByZXZpZXdVcmwiOiJzdGFjay1kYXNoYm9hcmQtZ2l0LWluaXQtc3RhY2stYW5hbHl0aWNzLW15LXRlYW0tMjBhNzk1MWYudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCJ9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **stack-backend** | ✅ Ready ([Inspect](https://vercel.com/my-team-20a7951f/stack-backend/HjknTkQVyELLsMNqy5yrgJd4RNVk)) | [Visit Preview](https://stack-backend-git-init-stack-analytics-my-team-20a7951f.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/stack-backend-git-init-stack-analytics-my-team-20a7951f.vercel.app?via=pr-comment-feedback-link) | Apr 8, 2025 8:07pm |
| **stack-dashboard** | ✅ Ready ([Inspect](https://vercel.com/my-team-20a7951f/stack-dashboard/Gj8pARbcNKVBkoUcMVVPStFxPfsn)) | [Visit Preview](https://stack-dashboard-git-init-stack-analytics-my-team-20a7951f.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/stack-dashboard-git-init-stack-analytics-my-team-20a7951f.vercel.app?via=pr-comment-feedback-link) | Apr 8, 2025 8:07pm |
| **stack-demo** | ✅ Ready ([Inspect](https://vercel.com/my-team-20a7951f/stack-demo/A1z4EuDLCydoX1vEnFEGatY59Ads)) | [Visit Preview](https://stack-demo-git-init-stack-analytics-my-team-20a7951f.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/stack-demo-git-init-stack-analytics-my-team-20a7951f.vercel.app?via=pr-comment-feedback-link) | Apr 8, 2025 8:07pm |




author_association: NONE
user: login: recurseml[bot]
created_at: 2025-04-07T18:36:16Z
body: 😱 Found 1 issue. Time to roll up your sleeves! 😱

<details>
<summary>🗒️ View all ignored comments in this repo</summary>

* The constraint 'TokenStoreType extends string' is too restrictive. It should likely be 'TokenStoreType extends string | object' to match the condition check in line 113 where TokenStoreType is checked against {}
* Return type mismatch - the interface declares useUsers() returning ServerUser[] but the Team interface that this extends declares useUsers() returning TeamUser[]
* There is a syntax error in the super constructor call due to the ellipsis operator used incorrectly. Objects aren't being merged correctly. This syntax usage can lead to runtime errors when trying to pass the merged object to 'super()'. Verify that the intended alterations to the object occur before or outside of the super() call if needed.
* Throwing an error when no active span is found is too aggressive. The log function should gracefully fallback to console.log or another logging mechanism when there's no active span, since not all execution contexts will have an active span. This makes the code less resilient and could break functionality in non-traced environments.

📚 [Relevant Docs](https://opentelemetry.io/docs/instrumentation/js/instrumentation)
* Function sets backendContext with a new configuration but doesn't pass 'defaultProjectKeys'. Since defaultProjectKeys is required in the type definition and cannot be updated (throws error if tried to set), this will cause a type error.
* The schema is using array syntax for pick() which is incorrect for Yup schemas. The pick() method in Yup expects individual arguments, not an array. Should be changed to: emailConfigSchema.pick('type', 'host', 'port', 'username', 'sender_name', 'sender_email')

📚 [Relevant Docs](https://github.com/jquense/yup#schemapcks-string-schema)
* Creating a refresh token with current timestamp as expiration means it expires immediately. Should set a future date for token expiration.
* The 'tools' object is initialized as an empty object, even though 'tools' is presumably expected to contain tool definitions. This could cause the server capabilities to lack necessary tool configurations, thus potentially impacting functionalities that depend on certain tool setups.

📚 [Relevant Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer)
* 'STACK_SECRET_SERVER_KEY' is potentially being included in every request header without checking its existence again here. Although it's checked during initialization, this could lead to security issues as it's exposed in all communications where the header is logged or captured.

📚 [Relevant Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)
* When adding 'use client' directive at the beginning, it doesn't check if file.text already contains the 'use client' directive. This could lead to duplicate 'use client' directives if the file already has one.

📚 [Relevant Docs](https://nextjs.org/docs/getting-started/react-essentials#the-use-client-directive)
</details>
author_association: NONE