Skip to content

feat: add telemetry abstraction#252

Open
Jonathangadeaharder wants to merge 5 commits into
mainfrom
feature/telemetry-abstraction
Open

feat: add telemetry abstraction#252
Jonathangadeaharder wants to merge 5 commits into
mainfrom
feature/telemetry-abstraction

Conversation

@Jonathangadeaharder

@Jonathangadeaharder Jonathangadeaharder commented May 8, 2026

Copy link
Copy Markdown
Owner

Closes #232. Single telemetry abstraction at src/lib/telemetry.ts — PostHog-backed with no-op fallback. Hooks into SvelteKit handle + handleError. Lazy-loads client/server SDKs.

Summary by CodeRabbit

Release Notes

New Features

  • Implemented telemetry infrastructure for event and pageview tracking across the application
  • Enhanced error reporting with unified error capture across client and server environments
  • Added graceful shutdown handling to ensure proper resource cleanup when the application terminates

Introduces src/lib/telemetry.ts as a unified interface for analytics
and error reporting. Backed by PostHog on both client and server with
a no-op fallback when PUBLIC_POSTHOG_PROJECT_TOKEN is unset.

- track(event, props?), pageview(route), captureError(err, ctx?)
- identify(userId, traits?), reset(), shutdown()
- hooks.server.ts: pageview tracking + error capture via abstraction
- hooks.client.ts: error capture via abstraction
- Graceful shutdown on SIGTERM
Copilot AI review requested due to automatic review settings May 8, 2026 08:05
@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Rate limit exceeded

@Jonathangadeaharder has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 17 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 18f46e78-5db3-458e-aeac-f05b42e1ee57

📥 Commits

Reviewing files that changed from the base of the PR and between 7b827fb and e013cbd.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml
📝 Walkthrough

Walkthrough

This PR implements a centralized telemetry abstraction layer in src/lib/telemetry.ts that serves as the sole entry point for PostHog event tracking, pageview recording, and error capture. Both client and server hooks are updated to route events through this wrapper instead of calling the vendor SDK directly, and server-side shutdown is coordinated gracefully.

Changes

Telemetry Abstraction and Integration

Layer / File(s) Summary
Telemetry Contract
src/lib/telemetry.ts
Exports six async helpers (track, pageview, captureError, identify, reset, shutdown) conditionally routed to lazily-initialized PostHog client (browser) or server instances, gated by PUBLIC_POSTHOG_PROJECT_TOKEN.
Client Error Reporting
src/hooks.client.ts
Imports captureError and calls it from handleError with { status, message, source: 'client' }, replacing direct PostHog exception calls while preserving return shape.
Server Pageview & Shutdown
src/hooks.server.ts
Imports telemetry helpers; triggers pageview() for non-/api and non-/_ routes; reports errors through captureError() with source: 'server'; adds SIGTERM/SIGINT listeners invoking shutdown() for graceful shutdown.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client App
  participant ClientHook as Client Hook
  participant Telemetry as Telemetry Layer
  participant PostHogBrowser as PostHog Browser
  participant Server as SvelteKit Server
  participant ServerHook as Server Hook
  participant PostHogNode as PostHog Node
  
  Client->>ClientHook: error thrown
  ClientHook->>Telemetry: captureError(error, {source: 'client'})
  Telemetry->>PostHogBrowser: capture exception
  
  Server->>ServerHook: HTTP request
  ServerHook->>Telemetry: pageview(route)
  Telemetry->>PostHogNode: capture $pageview
  ServerHook-->>Server: resolved response
  
  Note over Server: Process termination signal
  Server->>Telemetry: shutdown()
  Telemetry->>PostHogNode: close
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • Jonathangadeaharder/Tilt#243 — Both PRs modify telemetry initialization and error reporting in the same hooks files, adjusting how PostHog clients are instantiated and how errors/pageviews flow through the system.

Poem

🐰 A wrapper module hops so bright,
PostHog events now route just right,
Hooks no longer call the vendor,
Telemetry flows through one splendid sender,
Graceful shutdown on signal's flight!

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add telemetry abstraction' clearly and concisely describes the primary change in the PR: adding a telemetry abstraction layer.
Linked Issues check ✅ Passed The PR implements all required coding objectives from issue #232: telemetry.ts with track/pageview/captureError/identify/reset, lazy-loaded PostHog client/server SDKs, no-op fallback, SvelteKit integration, and client/server hook updates.
Out of Scope Changes check ✅ Passed All changes are within scope: telemetry.ts creation, hooks.server.ts and hooks.client.ts integration for pageviews/error-handling, and SIGTERM/SIGINT shutdown listeners.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/telemetry-abstraction

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

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request centralizes PostHog telemetry into a new src/lib/telemetry.ts module, updating client and server hooks to use unified tracking and error reporting. The review identifies several issues: potential double-counting of pageviews, a regression in user identification for server events, and performance concerns due to immediate event flushing. Suggestions were also provided to include error stack traces, handle SIGINT for graceful shutdowns, and optimize the shutdown logic to prevent unnecessary client re-initialization.

Comment thread src/hooks.server.ts
Comment on lines +51 to +53
if (!pathname.startsWith('/api') && !pathname.startsWith('/_')) {
pageview(pathname).catch(() => {});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Capturing a pageview on the server here will likely result in double-counting, as the PostHog client-side SDK also captures pageviews by default on the initial load. In a SvelteKit application, it is generally preferred to rely on client-side tracking for richer telemetry (e.g., browser info, screen resolution). If server-side tracking is intentionally desired, you should disable client-side auto-capture by setting capture_pageview: false in the PostHog initialization config.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed. Added capture_pageview: false to client PostHog init config to prevent double-counting. Server-side pageview tracking is intentionally kept for route-level analytics that client-side auto-capture misses (SSR navigation, bot traffic). hooks.server.ts:51-53.

Comment thread src/hooks.server.ts Outdated
Comment on lines +63 to +65
process.on('SIGTERM', () => {
shutdown().catch(() => {});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

It is recommended to handle SIGINT (e.g., when pressing Ctrl+C in a terminal) in addition to SIGTERM to ensure graceful shutdown and flushing of telemetry events during local development or manual restarts.

Suggested change
process.on('SIGTERM', () => {
shutdown().catch(() => {});
});
['SIGTERM', 'SIGINT'].forEach((signal) => {
process.on(signal, () => {
shutdown().catch(() => {});
});
});

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed. Added SIGINT alongside SIGTERM: ['SIGTERM', 'SIGINT'].forEach(signal => process.on(signal, () => shutdown().catch(() => {}))). hooks.server.ts:63-67.

Comment thread src/lib/telemetry.ts Outdated
ph?.capture(event, props);
} else {
const ph = await getServerPh();
ph?.capture({ distinctId: 'server', event, properties: props });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The new implementation hardcodes 'server' as the distinctId. The previous logic in hooks.server.ts attempted to use the user ID from the session (event.locals.session.user.id). This change is a regression in data quality, as it prevents attributing server-side events to specific users. Consider allowing an optional userId or distinctId to be passed to the telemetry functions.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed. track() now accepts an optional userId in props and passes it as distinctId on the server path: const distinctId = props?.userId ? String(props.userId) : 'server'. telemetry.ts:8-14. Full identify() is also available for explicit user attribution.

Comment thread src/lib/telemetry.ts
distinctId: 'server',
event: 'server_error',
properties: {
error: err instanceof Error ? err.message : String(err),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Including the error stack trace provides essential context for debugging server-side issues in PostHog. Currently, only the error message is captured.

Suggested change
error: err instanceof Error ? err.message : String(err),
error: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed. Added stack: err instanceof Error ? err.stack : undefined to server_error properties. telemetry.ts:38-39.

Comment thread src/lib/telemetry.ts Outdated
Comment on lines +65 to +67
const ph = await getServerPh();
await ph?.shutdown();
serverPhInstance = null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The shutdown function should check for the existence of serverPhInstance before calling getServerPh(). Otherwise, it will unnecessarily initialize a new PostHog instance just to immediately shut it down if one wasn't already active.

Suggested change
const ph = await getServerPh();
await ph?.shutdown();
serverPhInstance = null;
if (!serverPhInstance) return;
await serverPhInstance.shutdown();
serverPhInstance = null;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed. shutdown() now checks if (!serverPhInstance) return; before calling shutdown, avoiding unnecessary PostHog initialization. telemetry.ts:63-67.

Comment thread src/lib/telemetry.ts Outdated
Comment on lines +98 to +99
flushAt: 1,
flushInterval: 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Setting flushAt: 1 and flushInterval: 0 on the server-side client causes an HTTP request to be sent for every single event. This can lead to significant performance overhead under load. It is recommended to use the default batching behavior for better efficiency in a production environment.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed. Removed flushAt: 1 and flushInterval: 0 from server PostHog config. Now uses default batching behavior for production efficiency. telemetry.ts:97-99.

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Jonathan Gadea Harder added 3 commits May 8, 2026 12:47
- Replace 'any' types with proper typed imports (clientPhInstance,
  serverPhInstance)
- Fix import ordering in hooks.client.ts and hooks.server.ts (biome
  organizeImports)
- Add capture_pageview: false to client PostHog config to prevent
  double-counting
- Handle SIGINT alongside SIGTERM for graceful shutdown
- Add error stack trace to server_error events
- Guard shutdown() against initializing PostHog just to shut it down
- Remove flushAt:1/flushInterval:0 to use default batching
- Allow optional userId passthrough in track()

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
src/hooks.client.ts (1)

1-13: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Direct posthog-js initialization in hooks.client.ts violates the single-wrapper pattern.

This file still initializes PostHog directly, which bypasses $lib/telemetry and causes config drift (the wrapper sets capture_pageview: false, but this direct init does not). Additionally, the wrapper's __tilt_init flag prevents re-initialization, masking the architectural violation.

💡 Suggested direction

Remove the direct initialization and delegate to the telemetry wrapper:

-import posthog from 'posthog-js';
-import { PUBLIC_POSTHOG_HOST, PUBLIC_POSTHOG_PROJECT_TOKEN } from '$env/static/public';
-import { captureError } from '$lib/telemetry';
+import { captureError, initTelemetry } from '$lib/telemetry';

 export async function init() {
-	posthog.init(PUBLIC_POSTHOG_PROJECT_TOKEN, {
-		api_host: '/ingest',
-		ui_host: PUBLIC_POSTHOG_HOST,
-		defaults: '2026-01-30',
-		capture_exceptions: true,
-		person_profiles: 'identified_only'
-	});
+	await initTelemetry();
 }

Then add initTelemetry() to src/lib/telemetry.ts to expose SDK initialization in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks.client.ts` around lines 1 - 13, Remove the direct posthog-js
initialization in the exported init function (the posthog.init call and its
PUBLIC_* env usage) and instead call a single initialization entrypoint exported
from the telemetry wrapper (e.g. export/initTelemetry in src/lib/telemetry.ts).
Add an initTelemetry() function in telemetry.ts that performs the SDK init with
the wrapper’s canonical options (including capture_pageview: false,
capture_exceptions, person_profiles, api_host/ui_host) and respects the
wrapper’s __tilt_init reinitialization guard; then update the hooks.client.ts
init function to import and call initTelemetry() (and remove direct posthog
import and env var usage) so all PostHog configuration goes through
$lib/telemetry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks.server.ts`:
- Around line 63-67: The module-level process signal handlers using
['SIGTERM','SIGINT'] and process.on(...) around shutdown() are incompatible with
adapter-static and should be removed or gated; locate the signal registration
block that calls process.on(signal, () => { shutdown().catch(() => {}); }) and
either delete that block entirely or wrap it behind an environment/runtime check
(e.g., only register when using a Node runtime) so shutdown() is only wired when
running with a server adapter (adapter-node) or a proper server process.

In `@src/lib/telemetry.ts`:
- Around line 13-14: The current distinctId assignment can turn object userIds
into "[object Object]"; change the derivation so it only accepts primitive
userId values (string or number) and falls back to 'server' for anything else:
inspect props?.userId with typeof checks (e.g., === 'string' or === 'number')
before calling String(...) and use that safe distinctId when calling ph?.capture
(the variable currently declared as distinctId and the capture invocation).
- Around line 72-90: Replace the loose any typing for clientPhInstance with a
concrete PostHog type and a small local extension for the injected __tilt_init
flag: import the PostHog type from 'posthog-js', declare an interface (e.g.
TiltPostHog extends PostHog { __tilt_init?: boolean }), change
clientPhInstance's type to TiltPostHog | null and update getClientPh's return
type accordingly, and remove or adjust any (clientPhInstance as Record<string,
unknown>) casts to use the new TiltPostHog type so the client is strictly typed
like the server-side posthog-node usage.

---

Outside diff comments:
In `@src/hooks.client.ts`:
- Around line 1-13: Remove the direct posthog-js initialization in the exported
init function (the posthog.init call and its PUBLIC_* env usage) and instead
call a single initialization entrypoint exported from the telemetry wrapper
(e.g. export/initTelemetry in src/lib/telemetry.ts). Add an initTelemetry()
function in telemetry.ts that performs the SDK init with the wrapper’s canonical
options (including capture_pageview: false, capture_exceptions, person_profiles,
api_host/ui_host) and respects the wrapper’s __tilt_init reinitialization guard;
then update the hooks.client.ts init function to import and call initTelemetry()
(and remove direct posthog import and env var usage) so all PostHog
configuration goes through $lib/telemetry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 688b05c8-54cc-433e-b11d-d595d0a7b899

📥 Commits

Reviewing files that changed from the base of the PR and between c531a23 and 7b827fb.

⛔ Files ignored due to path filters (4)
  • .svelte-kit/ambient.d.ts is excluded by !**/.svelte-kit/**
  • .svelte-kit/generated/client/app.js is excluded by !**/.svelte-kit/**, !**/generated/**
  • .svelte-kit/generated/client/nodes/1.js is excluded by !**/.svelte-kit/**, !**/generated/**
  • .svelte-kit/generated/server/internal.js is excluded by !**/.svelte-kit/**, !**/generated/**
📒 Files selected for processing (3)
  • src/hooks.client.ts
  • src/hooks.server.ts
  • src/lib/telemetry.ts

Comment thread src/hooks.server.ts
Comment on lines +63 to +67
['SIGTERM', 'SIGINT'].forEach((signal) => {
process.on(signal, () => {
shutdown().catch(() => {});
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether this app targets Node-only runtime or an edge adapter
fd -i "svelte.config.*" --exec rg -n "adapter|kit"
rg -nP "@sveltejs/adapter|adapter-" package.json

Repository: Jonathangadeaharder/Tilt

Length of output: 244


Remove or refactor signal handlers for static adapter compatibility.

This codebase uses @sveltejs/adapter-static, which generates static sites. Module-level signal handlers like process.on(signal) are executed during the build phase, but they won't function in static deployments since there is no running server to catch signals. Either remove these handlers if unnecessary for static builds, or migrate to a Node.js adapter (e.g., @sveltejs/adapter-node) if server-side lifecycle management is required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks.server.ts` around lines 63 - 67, The module-level process signal
handlers using ['SIGTERM','SIGINT'] and process.on(...) around shutdown() are
incompatible with adapter-static and should be removed or gated; locate the
signal registration block that calls process.on(signal, () => {
shutdown().catch(() => {}); }) and either delete that block entirely or wrap it
behind an environment/runtime check (e.g., only register when using a Node
runtime) so shutdown() is only wired when running with a server adapter
(adapter-node) or a proper server process.

Comment thread src/lib/telemetry.ts
Comment on lines +13 to +14
const distinctId = props?.userId ? String(props.userId) : 'server';
ph?.capture({ distinctId, event, properties: props });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Harden distinctId derivation to avoid [object Object] identities.

Line 13 currently stringifies any props.userId, so object values collapse to [object Object] and can merge unrelated users in telemetry.

💡 Suggested fix
-export async function track(event: string, props?: Record<string, unknown>) {
+type TrackProps = Record<string, unknown> & { userId?: string | number };
+
+export async function track(event: string, props?: TrackProps) {
@@
-		const distinctId = props?.userId ? String(props.userId) : 'server';
+		const distinctId =
+			typeof props?.userId === 'string' || typeof props?.userId === 'number'
+				? String(props.userId)
+				: 'server';
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 13-13: 'props.userId' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=Jonathangadeaharder_Poker&issues=AZ4HM2V_Vn59jIeS8gE3&open=AZ4HM2V_Vn59jIeS8gE3&pullRequest=252

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/telemetry.ts` around lines 13 - 14, The current distinctId assignment
can turn object userIds into "[object Object]"; change the derivation so it only
accepts primitive userId values (string or number) and falls back to 'server'
for anything else: inspect props?.userId with typeof checks (e.g., === 'string'
or === 'number') before calling String(...) and use that safe distinctId when
calling ph?.capture (the variable currently declared as distinctId and the
capture invocation).

Comment thread src/lib/telemetry.ts
Comment on lines +72 to +90
let clientPhInstance: Record<string, any> | null = null;

async function getClientPh() {
if (!browser) return null;
if (clientPhInstance) return clientPhInstance;
const mod = await import('posthog-js');
clientPhInstance = mod.default;
if (!(clientPhInstance as Record<string, unknown>).__tilt_init) {
clientPhInstance.init(PUBLIC_POSTHOG_PROJECT_TOKEN, {
api_host: '/ingest',
ui_host: PUBLIC_POSTHOG_HOST,
defaults: '2026-01-30',
capture_pageview: false,
capture_exceptions: true,
person_profiles: 'identified_only'
});
(clientPhInstance as Record<string, unknown>).__tilt_init = true;
}
return clientPhInstance;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify explicit any usage and current client instance typing in telemetry wrapper
rg -nP --type=ts '\bany\b' src/lib/telemetry.ts
rg -nP --type=ts 'clientPhInstance|getClientPh\s*\(' src/lib/telemetry.ts

Repository: Jonathangadeaharder/Tilt

Length of output: 721


🏁 Script executed:

cat -n src/lib/telemetry.ts | head -100

Repository: Jonathangadeaharder/Tilt

Length of output: 3639


🏁 Script executed:

# Check if PostHog types are available or need to be inferred
rg -nP --type=ts 'import.*posthog|from.*posthog' src/lib/telemetry.ts

Repository: Jonathangadeaharder/Tilt

Length of output: 237


🏁 Script executed:

# Check how the return value of getClientPh() is used in the codebase
rg -B2 -A3 'await getClientPh\(\)' src/lib/telemetry.ts

Repository: Jonathangadeaharder/Tilt

Length of output: 813


Replace any-based client instance typing with concrete PostHog types.

Record<string, any> on line 72 violates strict typing requirements and leaks any through getClientPh(). This is inconsistent with the server-side client at line 93, which properly uses import('posthog-node').PostHog.

Suggested fix
-let clientPhInstance: Record<string, any> | null = null;
+type PostHogClient = typeof import('posthog-js').default;
+type TiltPostHogClient = PostHogClient & { __tilt_init?: boolean };
+let clientPhInstance: TiltPostHogClient | null = null;
 
-async function getClientPh() {
+async function getClientPh(): Promise<TiltPostHogClient | null> {
@@
-	if (!(clientPhInstance as Record<string, unknown>).__tilt_init) {
+	if (!clientPhInstance.__tilt_init) {
@@
-		(clientPhInstance as Record<string, unknown>).__tilt_init = true;
+		clientPhInstance.__tilt_init = true;
📝 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
let clientPhInstance: Record<string, any> | null = null;
async function getClientPh() {
if (!browser) return null;
if (clientPhInstance) return clientPhInstance;
const mod = await import('posthog-js');
clientPhInstance = mod.default;
if (!(clientPhInstance as Record<string, unknown>).__tilt_init) {
clientPhInstance.init(PUBLIC_POSTHOG_PROJECT_TOKEN, {
api_host: '/ingest',
ui_host: PUBLIC_POSTHOG_HOST,
defaults: '2026-01-30',
capture_pageview: false,
capture_exceptions: true,
person_profiles: 'identified_only'
});
(clientPhInstance as Record<string, unknown>).__tilt_init = true;
}
return clientPhInstance;
type PostHogClient = typeof import('posthog-js').default;
type TiltPostHogClient = PostHogClient & { __tilt_init?: boolean };
let clientPhInstance: TiltPostHogClient | null = null;
async function getClientPh(): Promise<TiltPostHogClient | null> {
if (!browser) return null;
if (clientPhInstance) return clientPhInstance;
const mod = await import('posthog-js');
clientPhInstance = mod.default;
if (!clientPhInstance.__tilt_init) {
clientPhInstance.init(PUBLIC_POSTHOG_PROJECT_TOKEN, {
api_host: '/ingest',
ui_host: PUBLIC_POSTHOG_HOST,
defaults: '2026-01-30',
capture_pageview: false,
capture_exceptions: true,
person_profiles: 'identified_only'
});
clientPhInstance.__tilt_init = true;
}
return clientPhInstance;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/telemetry.ts` around lines 72 - 90, Replace the loose any typing for
clientPhInstance with a concrete PostHog type and a small local extension for
the injected __tilt_init flag: import the PostHog type from 'posthog-js',
declare an interface (e.g. TiltPostHog extends PostHog { __tilt_init?: boolean
}), change clientPhInstance's type to TiltPostHog | null and update
getClientPh's return type accordingly, and remove or adjust any
(clientPhInstance as Record<string, unknown>) casts to use the new TiltPostHog
type so the client is strictly typed like the server-side posthog-node usage.

@sonarqubecloud

sonarqubecloud Bot commented May 8, 2026

Copy link
Copy Markdown

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.

[v1.0] telemetry abstraction (single wrapper)

2 participants