Skip to content

[CI] (gpt54-trial) next-js/15-app-router-saas#2401

Closed
wizard-ci-bot[bot] wants to merge 1 commit into
mainfrom
wizard-ci-gpt54-trial-next-js-15-app-router-saas
Closed

[CI] (gpt54-trial) next-js/15-app-router-saas#2401
wizard-ci-bot[bot] wants to merge 1 commit into
mainfrom
wizard-ci-gpt54-trial-next-js-15-app-router-saas

Conversation

@wizard-ci-bot

@wizard-ci-bot wizard-ci-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

Automated wizard CI run

Source: manual
Trigger ID: gpt54-trial
App: next-js/15-app-router-saas
App directory: apps/next-js/15-app-router-saas
Workbench branch: wizard-ci-gpt54-trial-next-js-15-app-router-saas
Wizard branch: 543fbd70b7d834c9bb2bf5afe28fc0a5761ced5d
Context Mill branch: main
PostHog (MCP) branch: master
Timestamp: 2026-07-03T16:12:03.982Z
Duration: 486.6s

YARA Scanner

✓ 63 tool calls scanned, 0 violations detected

No violations: ✓ 63 clean scans

@wizard-ci-bot

wizard-ci-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown
Author

Now I have all the context I need. Here's my evaluation:


PR Evaluation Report

Summary

This PR integrates PostHog into a Next.js 15 App Router SaaS app with both client-side (posthog-js via instrumentation-client.ts) and server-side (posthog-node) instrumentation. It adds a reverse proxy, user identification, posthog.reset() on logout, error tracking, and a comprehensive set of 12+ custom events covering authentication, team management, and Stripe subscription flows.

Files changed Lines added Lines removed
11 +222 -2

Confidence score: 3/5 🤔

  • PII in server-side capture() properties: email is passed as an event property in user_signed_in, user_signed_up, user_signed_out, account_deleted, and invited_email in team_member_invited. PII should only be set via identify() person properties, not in capture() event properties. [CRITICAL]
  • captureException called on posthog-node: In app/api/stripe/checkout/route.ts, posthog.captureException() is called on the Node SDK client, but captureException is a posthog-js browser method — it does not exist on posthog-node. This will cause a runtime error (or TypeScript build error). [CRITICAL]
  • Stripe webhook uses Stripe customer ID as distinctId: In app/api/stripe/webhook/route.ts, subscription.customer as string (a Stripe cus_xxx ID) is used as the distinctId, which won't match the numeric user IDs used everywhere else. These subscription events will create orphaned PostHog persons. [MEDIUM]
  • .env.example not updated: PostHog environment variables (NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN, NEXT_PUBLIC_POSTHOG_HOST) are not added to .env.example. [MEDIUM]

File changes

Filename Score Description
instrumentation-client.ts 4/5 Client-side PostHog init with reverse proxy, error tracking, and debug mode
lib/posthog-server.ts 4/5 Server-side PostHog client factory with correct flush settings
next.config.ts 5/5 Reverse proxy rewrites with correct static/array/ingest routing
package.json 5/5 Both posthog-js and posthog-node added
app/(dashboard)/layout.tsx 4/5 Client-side identify via SWR user fetch + reset on sign-out
app/(login)/actions.ts 3/5 Server-side events with PII in capture properties
app/(dashboard)/pricing/submit-button.tsx 4/5 Client-side checkout_initiated event
app/api/stripe/checkout/route.ts 2/5 Has invalid captureException call on posthog-node
app/api/stripe/webhook/route.ts 3/5 Uses Stripe customer ID as distinctId instead of app user ID
posthog-setup-report.md 4/5 Helpful setup report documenting events and next steps

App sanity check ⚠️

Criteria Result Description
App builds and runs No posthog.captureException() does not exist on posthog-node — will cause a TypeScript type error or runtime crash in the Stripe checkout error handler
Preserves existing env vars & configs Yes Existing configs preserved; only PostHog additions made
No syntax or type errors No captureException is not a valid method on the posthog-node PostHog class
Correct imports/exports Yes posthog-js used client-side, posthog-node used server-side — correct separation
Minimal, focused changes Yes All changes directly relate to PostHog integration
Pre-existing issues None No pre-existing issues observed

Issues

  • captureException on posthog-node: posthog.captureException(error) is called on the posthog-node client in app/api/stripe/checkout/route.ts. This method only exists on posthog-js (browser SDK). The Node SDK should use posthog.capture({ distinctId, event: '', properties: { ... } }) or handle errors differently. [CRITICAL]
  • .env.example missing PostHog vars: NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN and NEXT_PUBLIC_POSTHOG_HOST are required but not documented in .env.example. Collaborators won't know what to set. [MEDIUM]

Other completed criteria

  • All changes relate to PostHog integration with no unrelated edits
  • Correct files modified for Next.js App Router (instrumentation-client.ts, next.config.ts, layout, server actions, API routes)
  • Code follows existing codebase patterns (naming, structure, TypeScript usage)
  • Both posthog-js and posthog-node correctly imported from their respective packages
  • Build configuration (package.json) is valid with correct dependency additions

PostHog implementation ⚠️

Criteria Result Description
PostHog SDKs installed Yes posthog-js@^1.396.6 and posthog-node@^5.39.4 added to package.json
PostHog client initialized Yes Client via instrumentation-client.ts with posthog.init(); server via factory function creating new PostHog instance per request with flushAt: 1, flushInterval: 0
capture() Yes 12+ meaningful events across client and server
identify() Yes Client-side via PostHogIdentify component using SWR; server-side in signIn/signUp/updateAccount actions using user.id.toString()
Error tracking No capture_exceptions: true in client init is correct, but server-side captureException() call on posthog-node is invalid
Reverse proxy Yes Next.js rewrites correctly configured: /ingest/static/* and /ingest/array/*us-assets.i.posthog.com, /ingest/*us.i.posthog.com

Issues

  • Invalid captureException on server: The Stripe checkout error handler calls posthog.captureException() on the posthog-node client, which doesn't have this method. Use posthog.capture({ distinctId: userId, event: '', properties: { : error.message } }) instead. [CRITICAL]
  • Stripe webhook distinctId mismatch: subscription.customer as string yields a Stripe customer ID (e.g., cus_xxx), not the app's numeric user ID. These events won't link to the correct PostHog person. Should look up the app user by Stripe customer ID first. [MEDIUM]

Other completed criteria

  • API key loaded from environment variable NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN
  • Host correctly configured — client uses /ingest reverse proxy, server uses NEXT_PUBLIC_POSTHOG_HOST env var
  • posthog.reset() called on sign-out to unlink future events
  • PostHogIdentify component re-identifies on app load (returning users)
  • Server-side identify() in signIn/signUp uses real database user IDs
  • skipTrailingSlashRedirect: true correctly added for reverse proxy compatibility

PostHog insights and events ⚠️

Filename PostHog events Description
instrumentation-client.ts autocapture, ``, captureException (auto) Client-side init with error autocapture enabled
app/(dashboard)/layout.tsx identify, reset User identification on dashboard load, reset on sign-out
app/(dashboard)/pricing/submit-button.tsx checkout_initiated Tracks when user clicks checkout button
app/(login)/actions.ts user_signed_in, user_signed_up, user_signed_out, password_updated, account_deleted, account_updated, team_member_removed, team_member_invited Server-side auth and team management events
app/api/stripe/checkout/route.ts subscription_checkout_completed, captureException Checkout completion and error tracking (broken)
app/api/stripe/webhook/route.ts subscription_updated, subscription_cancelled Stripe webhook subscription lifecycle events

Issues

  • PII in capture() event properties: email appears as an event property in user_signed_in, user_signed_up, user_signed_out, and account_deleted. invited_email appears in team_member_invited. Email is PII and belongs in identify() person properties, not in capture() event properties. Remove email from all capture() calls — the user is already identified via identify(). [CRITICAL]

Other completed criteria

  • Events represent real user actions mapping to actual SaaS product flows (auth, billing, team management)
  • Events enable product insights — can build sign-up → checkout conversion funnel, retention analysis, churn signals from account_deleted/subscription_cancelled
  • Events include relevant contextual properties (team_id, plan_name, subscription_status, stripe IDs)
  • Event names are descriptive and use consistent snake_case naming convention

Reviewed by wizard workbench PR evaluator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants