PostHog / PostHog/wizard

Re-running the wizard on an already-instrumented project overwrites deliberate user configuration

Open
#1,121 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
197
Forks
51
Avg merge
1d 23h
Merged PRs (30d)
94

Description

Problem

#616 describes the detection gap and quotes a user who feared the wizard "was about to start changing whatever I had set up previously." I ran the full default flow on an already-instrumented project to see what actually happens on that path. The fear is justified: re-instrumentation undoes deliberate, hand-written configuration — including a setting PostHog's own docs require.

Setup: Next.js 16.2.10 app deployed to Cloudflare Workers via @opennextjs/cloudflare 1.20.1, with a hand-tuned PostHog integration already in place: reverse proxy through Next rewrites (/ingest), an identify component with an intentional dedupe guard, and a server-side posthog-node client. Wizard 2.64.1.

To be fair up front: the event instrumentation the wizard added was genuinely good — sensible snake_case taxonomy with useful properties (scene_added {scene_type}, new_film_started {creation_method}), captures placed in the state-owning hooks rather than the UI layer, and it even gated one capture on a workflow's APPLIED status instead of capturing blindly. The problem is what it did to the code that already existed.

What got overwritten

1. Removed ui_host while keeping the reverse proxy
 posthog.init(token, {
   api_host: "/ingest",
-  ui_host: "https://us.posthog.com",
+  tracing_headers: [window.location.hostname],
   defaults: "2026-01-30",
   capture_exceptions: true,

The reverse proxy docs explicitly say to set ui_host when api_host points at a proxy, so the toolbar and app links don't resolve against the proxy. The wizard kept my api_host: "/ingest" (good) but dropped the ui_host that goes with it. As far as I can tell the only reference to ui_host in this repo is an e2e fixture, so nothing currently asserts it survives a run.

(The tracing_headers addition itself is fine — I verified it's a real option in posthog-js 1.407.2.)

2. Deleted hand-written comments explaining why

The init file had comments documenting the reverse-proxy rationale ("rewrites via the worker so ad-blockers don't eat events") and the error-tracking choice. The identify component had a comment explaining what it links. All dropped, while the surrounding code was kept. Comments are the strongest intent signal a hand-written integration has, and they didn't survive the rewrite.

3. Removed an intentional identify guard
 export function PostHogIdentify({ userId, email }: { userId: string; email?: string | null }) {
   useEffect(() => {
-    if (posthog.get_distinct_id() !== userId) {
-      posthog.identify(userId, email ? { email } : undefined);
-    }
+    posthog.identify(userId, email ? { email } : undefined);
   }, [userId, email]);

The component is rendered from server pages, so it mounts on every navigation. With the guard gone, identify() (with a properties payload) now fires per navigation, sending redundant $set events. The guard was deliberate.

4. Silently dropped an explicit host fallback in the server client
     client = new PostHog(key, {
-      host: env?.NEXT_PUBLIC_POSTHOG_HOST ?? process.env.NEXT_PUBLIC_POSTHOG_HOST ?? "https://us.i.posthog.com",
+      host: env?.NEXT_PUBLIC_POSTHOG_HOST ?? process.env.NEXT_PUBLIC_POSTHOG_HOST,

Behavior now depends on the env var being set, with no stated reason for the change. (On Workers, env access is request-scoped, which is why the explicit fallback existed.)

5. Minor, in code it added: logout capture races a full-page POST
<form action="/auth/logout" method="post"
  onSubmit={() => { posthog.capture("user_logged_out"); posthog.reset(); }}>

The form immediately navigates, so the capture can be lost in the unload, and reset() competes with the flush. (Adding reset() on logout was the right call — the ordering is the issue.)

Why this happens

The wizard has no way to distinguish "deliberate user decision" from "default it should improve." A hand-tuned ui_host looks identical to leftover boilerplate, and the comments that would disambiguate get deleted rather than read as intent. On greenfield this doesn't matter; on the exact path #616 worries about, it does.

Suggested behavior

  • Treat existing, working integration code as load-bearing: don't remove config options, guards, or comments the wizard didn't author unless it can say why.
  • Preserve comments verbatim when editing existing files.
  • When changing existing config is genuinely warranted, surface it as an explicit per-change "changed X because Y" in the review step, rather than a silent rewrite.
  • This pairs with #616: its proposed "Re-run integration anyway" escape hatch is only safe if this path is non-destructive.

Happy to share the full diff of the run if useful.

Environment

  • @posthog/wizard 2.64.1 (via npx)
  • Next.js 16.2.10, @opennextjs/cloudflare 1.20.1, wrangler 4.112.0
  • posthog-js 1.407.2, posthog-node 5.46.0
  • Node v22.23.2, Linux

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the wizard logic that rewrites existing integration files and the e2e fixture that references ui_host, then compare how existing configuration and comments are handled. Done should mean re-running on an instrumented project preserves user-owned options, guards, and comments, while any intentional changes are explained explicitly.

Written by the indexing model from the issue text.

Assessment

Tech stack
nextjs, typescript
Domain
developer-experience, tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.