Skip to content

Tracking student behaviour with initial telemetry implementation#12

Merged
millecodex merged 7 commits into
mainfrom
feat/observability-frontend
Jun 23, 2026
Merged

Tracking student behaviour with initial telemetry implementation#12
millecodex merged 7 commits into
mainfrom
feat/observability-frontend

Conversation

@Ronaldo93

@Ronaldo93 Ronaldo93 commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features

    • Added error tracking and monitoring to improve reliability.
    • Integrated PostHog analytics across the app, including authentication, course activity, code actions, and chat interactions.
  • Documentation

    • Added/expanded step-by-step documentation for integrating PostHog with TanStack Start, including setup references and a generated PostHog setup report template.

@Ronaldo93 Ronaldo93 linked an issue Jun 22, 2026 that may be closed by this pull request
2 tasks
@Ronaldo93 Ronaldo93 changed the title feat: initialize telemetry with sentry and posthog. Tracking student behaviour with initial telemetry implementation Jun 22, 2026
@Ronaldo93 Ronaldo93 requested a review from millecodex June 22, 2026 13:22
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 51e764e3-11b3-420b-b3c2-fc8e8b545ac4

📥 Commits

Reviewing files that changed from the base of the PR and between f3e107b and 544b568.

⛔ Files ignored due to path filters (1)
  • server/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • server/.env.example
  • server/main.py
  • server/pyproject.toml
✅ Files skipped from review due to trivial changes (1)
  • server/pyproject.toml

📝 Walkthrough

Walkthrough

Adds PostHog analytics to the ExemplAI web frontend (client PostHogProvider, server-side singleton, auth event tracking, course interaction events, Vite dev proxy) and integrates Sentry SDK with metrics into the Python execution server. Also adds Claude AI skill documentation for a PostHog TanStack Start integration workflow.

Changes

Server Sentry Metrics

Layer / File(s) Summary
Sentry SDK dependency, env config, and initialization
server/pyproject.toml, server/.env.example, server/main.py
Adds sentry-sdk>=2.62.0 dependency, SENTRY_DSN env placeholder, Sentry initialization with send_default_pii=True, and a code.execution metrics counter in the /execute handler.

Web PostHog Analytics Integration

Layer / File(s) Summary
PostHog dependencies, dev-server proxy, and root PostHogProvider
web/package.json, web/pnpm-workspace.yaml, web/vite.config.ts, web/src/routes/__root.tsx
Adds @posthog/react and posthog-node packages, disables core-js builds, adds three Vite dev-server proxy routes stripping /api/v1 prefix for PostHog ingestion, and wraps the app root with PostHogProvider from env variables.
Server-side PostHog singleton
web/src/utils/posthog-server.ts
New module exports getPostHogClient() lazily creating and caching a PostHog (posthog-node) instance with flushAt: 1 and flushInterval: 0.
Auth flow event tracking
web/src/components/auth/forms/MagicLinkForm.tsx, web/src/components/auth/forms/SignInForm.tsx, web/src/components/auth/forms/SignUpForm.tsx, web/src/routes/auth.tsx
Instruments all auth forms with identify on sign-in/sign-up, capture for user_signed_in, user_signed_up, magic_link_requested, invitation_code_submitted, user_signed_out, and captureException in catch paths; calls posthog.reset() on sign-out.
Course interaction event tracking
web/src/components/home/CourseList.tsx, web/src/routes/_authenticated.course.tsx
Captures problem_started with metadata in ShortProblemCard; extends search to match problem tags; captures code_reset, code_run, code_submitted (with success flag), ai_chat_opened, and exceptions in the course workspace.
Claude PostHog skill docs and setup report
web/.claude/skills/integration-tanstack-start/ (SKILL.md, references/*), web/posthog-setup-report.md
Adds a four-phase Claude integration skill (begin/edit/revise/conclude) with EXAMPLE.md reference project, identify-users.md, tanstack-start.md tutorial docs, and a wizard-generated setup report with event catalog and verification checklist.

Sequence Diagram

sequenceDiagram
  participant User as User
  participant WebApp as Web App
  participant PostHog as PostHog Analytics
  participant Server as Python Server
  
  User->>WebApp: sign up / sign in
  WebApp->>PostHog: identify(user_id, email)
  WebApp->>PostHog: capture(user_signed_in / user_signed_up)
  
  User->>WebApp: click problem link
  WebApp->>PostHog: capture(problem_started, metadata)
  
  User->>WebApp: run/submit code
  WebApp->>Server: POST /execute (code)
  Server->>PostHog: metrics.count(code.execution)
  Server-->>WebApp: execution result
  WebApp->>PostHog: capture(code_run / code_submitted, success)
  
  User->>WebApp: sign out
  WebApp->>PostHog: capture(user_signed_out)
  WebApp->>PostHog: reset()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • RMIT-BDSL/ExemplAI#2: Implements the foundational FastAPI /execute endpoint in server/main.py that this PR extends with Sentry initialization and metrics.count side effects.

Poem

🐇 Hippity hop, analytics pop!
Events now fly when code goes run,
identify the user, capture the fun,
Sentry watches the server with care,
PostHog sees every click, click, there~
This bunny's quite pleased with the tracking we've done! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% 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 accurately describes the main objective of the PR: implementing telemetry for tracking student behavior across the application.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/observability-frontend

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

@Ronaldo93

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 12

🧹 Nitpick comments (2)
web/src/routes/__root.tsx (1)

51-53: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid non-null assertion for the PostHog key at app bootstrap.

Line 52 forces VITE_PUBLIC_POSTHOG_PROJECT_TOKEN!; if missing, runtime init can become undefined-driven and harder to diagnose. Add an explicit guard.

Suggested patch
 function RootDocument({ children }: { children: React.ReactNode }) {
+  const posthogKey = import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN;
+  const app = <ConvexProvider client={convex}>{children}</ConvexProvider>;
+
   return (
@@
-        <PostHogProvider
-          apiKey={import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN!}
+        {posthogKey ? (
+        <PostHogProvider
+          apiKey={posthogKey}
           options={{
@@
-          <ConvexProvider client={convex}>{children}</ConvexProvider>
-        </PostHogProvider>
+          {app}
+        </PostHogProvider>
+        ) : (
+          app
+        )}
🤖 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 `@web/src/routes/__root.tsx` around lines 51 - 53, The PostHogProvider
initialization in the root route is using a non-null assertion (!) on the
VITE_PUBLIC_POSTHOG_PROJECT_TOKEN environment variable, which can lead to
undefined-driven runtime errors if the token is missing. Replace the non-null
assertion with an explicit guard check by verifying that the environment
variable exists and is not empty before passing it to the apiKey prop of
PostHogProvider. If the token is missing, handle it appropriately (either skip
initialization, provide a fallback value, or throw a clear error during
bootstrap).
web/.claude/skills/integration-tanstack-start/references/tanstack-start.md (1)

54-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Bump the defaults snapshot.

This example pins an older config baseline; PostHog's current JS docs use defaults: '2026-05-30', so updating the snapshot keeps the tutorial aligned with the SDK docs. (posthog.com)

🤖 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 `@web/.claude/skills/integration-tanstack-start/references/tanstack-start.md`
around lines 54 - 60, Update the defaults configuration snapshot in the
PostHogProvider component to align with PostHog's current JavaScript SDK
documentation. Locate the PostHogProvider component with the options object and
change the defaults property value from '2026-01-30' to '2026-05-30' to match
the current PostHog JS docs baseline.
🤖 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 `@server/main.py`:
- Around line 16-21: The sentry_sdk.init() call currently has
send_default_pii=True which captures user-identifying information (headers, IP,
user data) in all Sentry events, creating unnecessary privacy and compliance
exposure especially for the /execute flow that processes student code. Change
send_default_pii to False or remove it entirely since False is the default
behavior, ensuring PII is not broadly captured globally. If PII capture is
needed for specific debugging scenarios in the future, implement it selectively
using Sentry's before_send hook instead of enabling broad capture.

In `@web/.claude/skills/integration-tanstack-start/references/EXAMPLE.md`:
- Around line 14-21: Remove features from the Features list in the EXAMPLE.md
file that are not actually implemented or demonstrated in the example code.
Specifically, remove "Session replay" from the bulleted feature list since
session replay is not configured or demonstrated in the demo. Keep only the
features that are fully implemented and can be copied/used by readers following
this example, such as product analytics, error tracking, user authentication,
server-side and client-side tracking, and the reverse proxy configuration.
- Around line 80-119: The PostHog configuration examples in EXAMPLE.md contain
incorrect endpoint paths and regions that do not match the actual app setup. In
the PostHogProvider configuration, the api_host is set to `/ingest` but should
be `/api/v1`, and the ui_host uses US region endpoints (https://us.posthog.com)
but should use EU region endpoints (https://eu.posthog.com). The same incorrect
paths and regions appear in the getPostHogClient function's server-side setup.
Update all PostHog endpoint paths and region references throughout EXAMPLE.md
(including the Vite proxy setup mentioned in lines 157-172) to use `/api/v1` for
the API path and the EU region URLs (https://eu.posthog.com and
https://eu.i.posthog.com) instead of the US region variants.

In `@web/.claude/skills/integration-tanstack-start/references/identify-users.md`:
- Line 172: The anchor link at line 172 that points to
`#1-call-identify-as-soon-as-youre-able` does not match the heading at line 85
which is "### 1. Call `identify` as soon as you're able to". Update the anchor
link in the text "call `identify` [as soon as you're
able](`#1-call-identify-as-soon-as-youre-able`)" by changing the target anchor to
`#1-call-identify-as-soon-as-youre-able-to` to include the trailing "to" that
appears in the actual heading.

In `@web/.claude/skills/integration-tanstack-start/references/tanstack-start.md`:
- Around line 20-25: The "Identifying users" section in the markdown file
currently only mentions calling posthog.identify() after login, but it should
also include app load as a recommended time to identify users. Update the text
to clarify that identify should be called both on first app load and after login
to ensure returning sessions are properly linked to known user profiles. This
will align the documentation with PostHog's best practices and prevent anonymous
sessions from becoming disconnected when users return to the application.
- Around line 163-174: The posthog.capture() call uses body.userId from the
untrusted request body for the distinctId parameter, which allows callers to
spoof user IDs and creates orphaned events in PostHog. Instead of reading the
user ID from the request body, retrieve the authenticated user ID from the
server's authenticated context or session state and pass that as the distinctId
value in the posthog.capture() call. This ensures the purchase event is
correctly attributed to the actual authenticated user and matches the frontend
user ID from PostHog's identify() call.

In `@web/.claude/skills/integration-tanstack-start/SKILL.md`:
- Around line 27-28: The description for the `references/4-conclude.md` entry is
incorrectly labeled as another review and fix pass, which is identical to the
previous step description on line 27. Update the description on line 28 to
reflect that this is a conclusion or wrap-up step rather than another review/fix
phase, ensuring the workflow progression is clear and the conclude step is
distinct from the revise step.

In `@web/src/components/auth/forms/MagicLinkForm.tsx`:
- Around line 36-38: The console.log statement in the MagicLinkForm component
logs the user's email directly to the client console, which exposes PII. Remove
the console.log statement that logs the message "Magic Link Sign In initiated
for email" along with the value.email. If logging is needed for debugging,
replace it with a log message that does not include the email address or any
other sensitive user information.

In `@web/src/routes/_authenticated.course.tsx`:
- Around line 97-99: The telemetry events in handleReset() function and the
other functions at lines 129-133 and 196-198 are using the potentially undefined
problemId in their posthog.capture calls, which results in incomplete analytics
records. Instead of using problemId directly, use the resolved active question
id value that is established in the fallback logic around lines 51-55. Update
all posthog.capture calls (in handleReset() and the other affected functions) to
use this resolved value instead of the raw problemId parameter to ensure
complete and valid telemetry payloads.

In `@web/src/routes/auth.tsx`:
- Around line 129-132: The telemetry events posthog.capture("user_signed_out")
and posthog.reset() are being recorded before the authClient.signOut() call
completes, which means analytics will report a successful sign-out even if the
actual sign-out operation fails. Move both the posthog.capture and posthog.reset
calls to execute after the await authClient.signOut() completes successfully, so
they only record if the sign-out actually succeeds. Consider wrapping the
sign-out operation in a try-catch block to handle failure scenarios
appropriately.

In `@web/src/utils/posthog-server.ts`:
- Around line 8-13: Replace the `host` configuration in the PostHog client
initialization (lines 12-13 in posthog-server.ts) to use a separate environment
variable VITE_PUBLIC_POSTHOG_API_HOST instead of VITE_PUBLIC_POSTHOG_HOST. This
ensures server-side ingestion uses the correct ingestion endpoint while keeping
the dashboard endpoint separate for client-side usage. Update the fallback chain
to check process.env.VITE_PUBLIC_POSTHOG_API_HOST first, then
import.meta.env.VITE_PUBLIC_POSTHOG_API_HOST.

In `@web/vite.config.ts`:
- Around line 23-40: Remove the `secure: false` property from all three proxy
server configurations in the vite.config.ts file. Specifically, delete `secure:
false` from the "^/api/v1/static" proxy configuration, the "^/api/v1/array"
proxy configuration, and the "^/api/v1" proxy configuration. This will re-enable
TLS certificate validation for HTTPS connections to PostHog services, which is
the secure default behavior.

---

Nitpick comments:
In `@web/.claude/skills/integration-tanstack-start/references/tanstack-start.md`:
- Around line 54-60: Update the defaults configuration snapshot in the
PostHogProvider component to align with PostHog's current JavaScript SDK
documentation. Locate the PostHogProvider component with the options object and
change the defaults property value from '2026-01-30' to '2026-05-30' to match
the current PostHog JS docs baseline.

In `@web/src/routes/__root.tsx`:
- Around line 51-53: The PostHogProvider initialization in the root route is
using a non-null assertion (!) on the VITE_PUBLIC_POSTHOG_PROJECT_TOKEN
environment variable, which can lead to undefined-driven runtime errors if the
token is missing. Replace the non-null assertion with an explicit guard check by
verifying that the environment variable exists and is not empty before passing
it to the apiKey prop of PostHogProvider. If the token is missing, handle it
appropriately (either skip initialization, provide a fallback value, or throw a
clear error during bootstrap).
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e63b103f-44fa-4e23-a026-ac6adc84ea45

📥 Commits

Reviewing files that changed from the base of the PR and between bda91d0 and f3e107b.

⛔ Files ignored due to path filters (2)
  • server/uv.lock is excluded by !**/*.lock
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • server/.env.example
  • server/main.py
  • server/pyproject.toml
  • web/.claude/skills/integration-tanstack-start/.posthog-wizard
  • web/.claude/skills/integration-tanstack-start/SKILL.md
  • web/.claude/skills/integration-tanstack-start/references/1-begin.md
  • web/.claude/skills/integration-tanstack-start/references/2-edit.md
  • web/.claude/skills/integration-tanstack-start/references/3-revise.md
  • web/.claude/skills/integration-tanstack-start/references/4-conclude.md
  • web/.claude/skills/integration-tanstack-start/references/EXAMPLE.md
  • web/.claude/skills/integration-tanstack-start/references/identify-users.md
  • web/.claude/skills/integration-tanstack-start/references/tanstack-start.md
  • web/package.json
  • web/pnpm-workspace.yaml
  • web/posthog-setup-report.md
  • web/src/components/auth/forms/MagicLinkForm.tsx
  • web/src/components/auth/forms/SignInForm.tsx
  • web/src/components/auth/forms/SignUpForm.tsx
  • web/src/components/home/CourseList.tsx
  • web/src/routes/__root.tsx
  • web/src/routes/_authenticated.course.tsx
  • web/src/routes/auth.tsx
  • web/src/utils/posthog-server.ts
  • web/vite.config.ts

Comment thread server/main.py
Comment on lines +16 to +21
sentry_sdk.init(
dsn=os.getenv('SENTRY_DSN'),
# Add data like request headers and IP for users,
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
send_default_pii=True,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all Sentry initialization options and whether any scrubbing hooks are configured.
rg -n -C3 'sentry_sdk\.init|send_default_pii|before_send|before_send_transaction' server

# Verify potential sensitive payload flow into /execute and related logging.
rg -n -C3 'def judge0_execution|StudentCode|log\.info\(|requests\.post\(' server/main.py

Repository: RMIT-BDSL/ExemplAI

Length of output: 1409


🏁 Script executed:

# Check if Sentry DSN is conditionally used or if there are environment checks
rg -n 'SENTRY_DSN|sentry_sdk' server/main.py | head -20

# Check requirements or dependencies for sentry-sdk version
fd -e txt -e cfg -e toml -e yaml | xargs grep -l -i sentry 2>/dev/null | head -5

# Verify if there's any other privacy-related configuration in the main app initialization
rg -n -C5 'def|class|FastAPI' server/main.py | head -40

Repository: RMIT-BDSL/ExemplAI

Length of output: 1195


🏁 Script executed:

# Check the pyproject.toml for sentry-sdk dependency info
cat server/pyproject.toml

# Verify if there's any environment-based configuration for Sentry elsewhere
find server -type f -name "*.py" -o -name "*.toml" -o -name "*.yml" -o -name "*.yaml" | xargs grep -l -i "sentry" 2>/dev/null

Repository: RMIT-BDSL/ExemplAI

Length of output: 478


Disable broad PII capture by default in Sentry init.

send_default_pii=True can attach user-identifying request context (headers, IP, user data) to all Sentry events. For the /execute flow which receives and processes student code, this creates unnecessary privacy/compliance exposure for telemetry data.

🔧 Proposed fix
 sentry_sdk.init(
     dsn=os.getenv('SENTRY_DSN'),
-    # Add data like request headers and IP for users,
-    # see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
-    send_default_pii=True,
+    # Keep default telemetry privacy-preserving unless explicitly required.
+    send_default_pii=False,
 )

If PII capture is later needed for specific debugging scenarios, use Sentry's before_send hook to selectively include data only when necessary, rather than enabling broad capture globally.

📝 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
sentry_sdk.init(
dsn=os.getenv('SENTRY_DSN'),
# Add data like request headers and IP for users,
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
send_default_pii=True,
)
sentry_sdk.init(
dsn=os.getenv('SENTRY_DSN'),
# Keep default telemetry privacy-preserving unless explicitly required.
send_default_pii=False,
)
🤖 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 `@server/main.py` around lines 16 - 21, The sentry_sdk.init() call currently
has send_default_pii=True which captures user-identifying information (headers,
IP, user data) in all Sentry events, creating unnecessary privacy and compliance
exposure especially for the /execute flow that processes student code. Change
send_default_pii to False or remove it entirely since False is the default
behavior, ensuring PII is not broadly captured globally. If PII capture is
needed for specific debugging scenarios in the future, implement it selectively
using Sentry's before_send hook instead of enabling broad capture.

Source: Coding guidelines

Comment on lines +14 to +21
## Features

- **Product analytics**: Track user events and behaviors
- **Session replay**: Record and replay user sessions
- **Error tracking**: Capture and track errors automatically
- **User authentication**: Demo login system with PostHog user identification
- **Server-side & client-side tracking**: Complete examples of both tracking methods
- **Reverse proxy**: PostHog ingestion through Vite dev server proxy

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Trim the feature list to the implemented demo.

session replay and feature flags are advertised here, but this example never configures or demonstrates either one. That overstates what readers can copy from the sample.

🤖 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 `@web/.claude/skills/integration-tanstack-start/references/EXAMPLE.md` around
lines 14 - 21, Remove features from the Features list in the EXAMPLE.md file
that are not actually implemented or demonstrated in the example code.
Specifically, remove "Session replay" from the bulleted feature list since
session replay is not configured or demonstrated in the demo. Keep only the
features that are fully implemented and can be copied/used by readers following
this example, such as product analytics, error tracking, user authentication,
server-side and client-side tracking, and the reverse proxy configuration.

Comment on lines +80 to +119
PostHog is initialized using `PostHogProvider` from `@posthog/react`. The provider wraps the entire app in the root shell component and handles calling `posthog.init()` automatically:

```typescript
import { PostHogProvider } from '@posthog/react'

<PostHogProvider
apiKey={import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN!}
options={{
api_host: '/ingest',
ui_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST || 'https://us.posthog.com',
defaults: '2025-05-24',
capture_exceptions: true,
debug: import.meta.env.DEV,
}}
>
{children}
</PostHogProvider>
```

### Server-side setup (utils/posthog-server.ts)

For server-side tracking, we use the `posthog-node` SDK with a singleton pattern:

```typescript
import { PostHog } from 'posthog-node'

export function getPostHogClient() {
if (!posthogClient) {
posthogClient = new PostHog(
process.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN || import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN!,
{
host: process.env.VITE_PUBLIC_POSTHOG_HOST || import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
flushAt: 1,
flushInterval: 0,
}
)
}
return posthogClient
}
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and read the EXAMPLE.md file
find . -name "EXAMPLE.md" -type f

Repository: RMIT-BDSL/ExemplAI

Length of output: 226


🏁 Script executed:

# Find the relevant config files
find . -name "__root.tsx" -o -name "vite.config.ts" | head -20

Repository: RMIT-BDSL/ExemplAI

Length of output: 205


🏁 Script executed:

# Check file structure around the web directory
ls -la web/ 2>/dev/null || echo "web directory info unavailable"

Repository: RMIT-BDSL/ExemplAI

Length of output: 1596


🏁 Script executed:

# Read EXAMPLE.md to see the lines in question
wc -l "./web/.claude/skills/integration-tanstack-start/references/EXAMPLE.md"

Repository: RMIT-BDSL/ExemplAI

Length of output: 231


🏁 Script executed:

# Read lines 80-119 from EXAMPLE.md
sed -n '80,119p' "./web/.claude/skills/integration-tanstack-start/references/EXAMPLE.md"

Repository: RMIT-BDSL/ExemplAI

Length of output: 1325


🏁 Script executed:

# Read lines 157-172 from EXAMPLE.md
sed -n '157,172p' "./web/.claude/skills/integration-tanstack-start/references/EXAMPLE.md"

Repository: RMIT-BDSL/ExemplAI

Length of output: 513


🏁 Script executed:

# Read the __root.tsx file to see PostHog configuration
cat -n "./web/src/routes/__root.tsx"

Repository: RMIT-BDSL/ExemplAI

Length of output: 2995


🏁 Script executed:

# Read vite.config.ts to see proxy configuration
cat -n "./web/vite.config.ts"

Repository: RMIT-BDSL/ExemplAI

Length of output: 1582


Update EXAMPLE.md to match the actual PostHog configuration.

The examples hardcode incorrect endpoint paths and regions. The app uses /api/v1 with EU region (https://eu.posthog.com, https://eu.i.posthog.com), but EXAMPLE.md shows /ingest with US region (https://us.posthog.com, https://us.i.posthog.com). This affects both the client-side PostHogProvider configuration and the Vite proxy setup.

Also applies to: 157-172

🤖 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 `@web/.claude/skills/integration-tanstack-start/references/EXAMPLE.md` around
lines 80 - 119, The PostHog configuration examples in EXAMPLE.md contain
incorrect endpoint paths and regions that do not match the actual app setup. In
the PostHogProvider configuration, the api_host is set to `/ingest` but should
be `/api/v1`, and the ui_host uses US region endpoints (https://us.posthog.com)
but should use EU region endpoints (https://eu.posthog.com). The same incorrect
paths and regions appear in the getPostHogClient function's server-side setup.
Update all PostHog endpoint paths and region references throughout EXAMPLE.md
(including the Vite proxy setup mentioned in lines 157-172) to use `/api/v1` for
the API path and the EU region URLs (https://eu.posthog.com and
https://eu.i.posthog.com) instead of the US region variants.


### 5\. Use deep links between platforms

We recommend you call `identify` [as soon as you're able](#1-call-identify-as-soon-as-youre-able), typically when a user signs up or logs in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n web/.claude/skills/integration-tanstack-start/references/identify-users.md | head -180 | tail -30

Repository: RMIT-BDSL/ExemplAI

Length of output: 1774


🏁 Script executed:

rg -n "call.*identify.*as soon" web/.claude/skills/integration-tanstack-start/references/identify-users.md -i

Repository: RMIT-BDSL/ExemplAI

Length of output: 430


🏁 Script executed:

rg -n "^#+.*call" web/.claude/skills/integration-tanstack-start/references/identify-users.md -i

Repository: RMIT-BDSL/ExemplAI

Length of output: 209


Fix the anchor link—the heading ends with "to" but the link omits it.

The heading at line 85 is ### 1\. Call identify as soon as you're able to, but the link at line 172 points to #1-call-identify-as-soon-as-youre-able (missing the trailing "to"). Update the anchor to #1-call-identify-as-soon-as-youre-able-to.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 172-172: Link fragments should be valid

(MD051, link-fragments)

🤖 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 `@web/.claude/skills/integration-tanstack-start/references/identify-users.md`
at line 172, The anchor link at line 172 that points to
`#1-call-identify-as-soon-as-youre-able` does not match the heading at line 85
which is "### 1. Call `identify` as soon as you're able to". Update the anchor
link in the text "call `identify` [as soon as you're
able](`#1-call-identify-as-soon-as-youre-able`)" by changing the target anchor to
`#1-call-identify-as-soon-as-youre-able-to` to include the trailing "to" that
appears in the actual heading.

Source: Linters/SAST tools

Comment on lines +20 to +25
## Identifying users

> **Identifying users is required.** Call `posthog.identify('your-user-id')` after login to link events to a known user. This is what connects frontend event captures, [session replays](/docs/session-replay.md), [LLM traces](/docs/ai-engineering.md), and [error tracking](/docs/error-tracking.md) to the same person — and lets backend events link back too.
>
> See our guide on [identifying users](/docs/getting-started/identify-users.md) for how to set this up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find and read the tanstack-start.md file
find . -name "tanstack-start.md" -type f

Repository: RMIT-BDSL/ExemplAI

Length of output: 233


🏁 Script executed:

# Also search for any PostHog documentation files locally
find . -name "*posthog*" -o -name "*identify*" | grep -i doc | head -20

Repository: RMIT-BDSL/ExemplAI

Length of output: 156


🌐 Web query:

PostHog identify method best practices when to call

💡 Result:

The best practice is to call the PostHog identify method as soon as you are able to, typically upon application load and immediately after a user logs in or creates an account [1][2][3]. Key guidelines for calling identify: 1. Timing and Sequence Call identify before any actions that require accurate user attribution, such as feature flag evaluations, experiments, or critical conversion events [4]. If you call identify after these events, they will be initially attributed to the anonymous ID, and you may encounter issues where feature flag hashes are calculated against the wrong identity [4]. 2. Frequency You only need to call identify once per session; avoid unnecessary repeat calls [1][3]. PostHog automatically ignores subsequent calls that use the same data if the page has not been reloaded [1][3]. 3. Maintaining Identity Identify links an anonymous session to a known user by merging the anonymous distinct_id with your stable, canonical user ID (such as a database UID or email) [1][4]. This allows PostHog to stitch together event history from before and after login [1]. 4. Data Completeness Whenever possible, pass all available person properties during the identify call to ensure the user's person profile in PostHog remains up to date [2][3]. 5. Cross-Platform Consistency Use the same distinct ID format across all platforms (e.g., web and mobile) to avoid creating fragmented person profiles [5][6]. If a user transitions between platforms (e.g., web to mobile), use alias or additional identify calls to link their identities explicitly [1][2]. 6. Avoiding Common Errors Do not use catch-all, non-unique IDs (like "system" or "null") [5][6]. Using such IDs will cause unrelated events to be merged into a single profile, leading to data inaccuracy, rate limiting, and increased costs [5][3][6].

Citations:


🏁 Script executed:

# Read the tanstack-start.md file around lines 20-25
sed -n '15,30p' ./web/.claude/skills/integration-tanstack-start/references/tanstack-start.md

Repository: RMIT-BDSL/ExemplAI

Length of output: 1019


🏁 Script executed:

# Get more context—read the entire file to understand the structure
wc -l ./web/.claude/skills/integration-tanstack-start/references/tanstack-start.md

Repository: RMIT-BDSL/ExemplAI

Length of output: 237


Broaden the identify guidance to include app load.

PostHog recommends calling identify as soon as you're able to—typically on first app load and after login—so returning sessions stay linked to known users. The current text only mentions "after login", which misses the opportunity to connect returning anonymous sessions to their known user profiles.

Reference

Official PostHog docs: "The best practice is to call the PostHog identify method as soon as you are able to, typically upon application load and immediately after a user logs in or creates an account."

🤖 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 `@web/.claude/skills/integration-tanstack-start/references/tanstack-start.md`
around lines 20 - 25, The "Identifying users" section in the markdown file
currently only mentions calling posthog.identify() after login, but it should
also include app load as a recommended time to identify users. Update the text
to clarify that identify should be called both on first app load and after login
to ensure returning sessions are properly linked to known user profiles. This
will align the documentation with PostHog's best practices and prevent anonymous
sessions from becoming disconnected when users return to the application.

Comment on lines +36 to +38
console.log(
`[Auth] Magic Link Sign In initiated for email: "${value.email}"`,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove raw email logging from client console.

Line 36 logs the user’s email directly. This is avoidable PII exposure in client logs and should be removed.

🔧 Suggested fix
-        console.log(
-          `[Auth] Magic Link Sign In initiated for email: "${value.email}"`,
-        );
🤖 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 `@web/src/components/auth/forms/MagicLinkForm.tsx` around lines 36 - 38, The
console.log statement in the MagicLinkForm component logs the user's email
directly to the client console, which exposes PII. Remove the console.log
statement that logs the message "Magic Link Sign In initiated for email" along
with the value.email. If logging is needed for debugging, replace it with a log
message that does not include the email address or any other sensitive user
information.

Comment on lines +97 to +99
function handleReset() {
posthog.capture("code_reset", { problem_id: problemId, language });
setCodeTemplates((prev) => ({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use the resolved active question id for telemetry payloads.

When problemId is absent (Line 51-55 fallback), these events send problem_id: undefined (Lines 98, 130, 197), which creates incomplete analytics records.

🔧 Suggested fix
   if (activeQuestion === null) {
     return (
       <div className="flex h-screen w-screen items-center justify-center bg-zinc-950 text-zinc-100">
         <p className="text-zinc-400">No problems available in the database.</p>
       </div>
     );
   }
+  const telemetryProblemId = problemId ?? activeQuestion._id;

   function handleReset() {
-    posthog.capture("code_reset", { problem_id: problemId, language });
+    posthog.capture("code_reset", { problem_id: telemetryProblemId, language });
     setCodeTemplates((prev) => ({
       ...prev,
       [language]: CODE_TEMPLATES[language as keyof typeof CODE_TEMPLATES],
@@
       setExecutionResult(response.data);
       posthog.capture(actionType === "run" ? "code_run" : "code_submitted", {
-        problem_id: problemId,
+        problem_id: telemetryProblemId,
         language,
         success: !response.data?.error,
       });
@@
               setIsChatCollapsed={(collapsed) => {
                 if (!collapsed)
-                  posthog.capture("ai_chat_opened", { problem_id: problemId });
+                  posthog.capture("ai_chat_opened", {
+                    problem_id: telemetryProblemId,
+                  });
                 setIsChatCollapsed(collapsed);
               }}

Also applies to: 129-133, 196-198

🤖 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 `@web/src/routes/_authenticated.course.tsx` around lines 97 - 99, The telemetry
events in handleReset() function and the other functions at lines 129-133 and
196-198 are using the potentially undefined problemId in their posthog.capture
calls, which results in incomplete analytics records. Instead of using problemId
directly, use the resolved active question id value that is established in the
fallback logic around lines 51-55. Update all posthog.capture calls (in
handleReset() and the other affected functions) to use this resolved value
instead of the raw problemId parameter to ensure complete and valid telemetry
payloads.

Comment thread web/src/routes/auth.tsx
Comment on lines +129 to 132
posthog.capture("user_signed_out");
posthog.reset();
await authClient.signOut();
navigate({ to: "/auth" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Emit sign-out telemetry only after successful sign-out.

Line 129 currently records user_signed_out before authClient.signOut() completes. If sign-out fails, analytics still records a successful sign-out event.

🔧 Suggested fix
   const handleSignOut = async () => {
     setIsSigningOut(true);
     try {
-      posthog.capture("user_signed_out");
-      posthog.reset();
       await authClient.signOut();
+      posthog.capture("user_signed_out");
+      posthog.reset();
       navigate({ to: "/auth" });
📝 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
posthog.capture("user_signed_out");
posthog.reset();
await authClient.signOut();
navigate({ to: "/auth" });
const handleSignOut = async () => {
setIsSigningOut(true);
try {
await authClient.signOut();
posthog.capture("user_signed_out");
posthog.reset();
navigate({ to: "/auth" });
🤖 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 `@web/src/routes/auth.tsx` around lines 129 - 132, The telemetry events
posthog.capture("user_signed_out") and posthog.reset() are being recorded before
the authClient.signOut() call completes, which means analytics will report a
successful sign-out even if the actual sign-out operation fails. Move both the
posthog.capture and posthog.reset calls to execute after the await
authClient.signOut() completes successfully, so they only record if the sign-out
actually succeeds. Consider wrapping the sign-out operation in a try-catch block
to handle failure scenarios appropriately.

Comment on lines +8 to +13
process.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN ||
import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN!,
{
host:
process.env.VITE_PUBLIC_POSTHOG_HOST ||
import.meta.env.VITE_PUBLIC_POSTHOG_HOST,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and read the posthog-server.ts file
find . -name "posthog-server.ts" -type f

Repository: RMIT-BDSL/ExemplAI

Length of output: 190


🏁 Script executed:

# Also find the root provider file
find . -name "__root.tsx" -type f

Repository: RMIT-BDSL/ExemplAI

Length of output: 184


🏁 Script executed:

# Search for all VITE_PUBLIC_POSTHOG usage in the codebase
rg "VITE_PUBLIC_POSTHOG" -n

Repository: RMIT-BDSL/ExemplAI

Length of output: 156


🏁 Script executed:

cat -n ./web/src/utils/posthog-server.ts | head -30

Repository: RMIT-BDSL/ExemplAI

Length of output: 805


🏁 Script executed:

cat -n ./web/src/routes/__root.tsx | grep -A 10 -B 10 -i "posthog\|VITE_PUBLIC_POSTHOG"

Repository: RMIT-BDSL/ExemplAI

Length of output: 2034


🌐 Web query:

PostHog Node SDK host parameter ingestion endpoint

💡 Result:

In the PostHog Node.js SDK, the host parameter is used to specify the base URL for the PostHog instance [1][2]. When initializing the PostHog client, you can pass this parameter within the options object to define where your events are sent [3][4]. The default value for the host is https://us.i.posthog.com/ [1][2]. Example initialization: import { PostHog } from 'posthog-node' const client = new PostHog('<ph_project_token>', { host: 'https://us.i.posthog.com' }) It is important to note the distinction between an app host (e.g., https://app.posthog.com) and an ingestion host (e.g., https://us.i.posthog.com) [5]. While the SDK documentation sometimes shows examples using https://app.posthog.com [3][6], the host parameter should generally point to the appropriate ingestion endpoint for your region or setup to ensure events are processed correctly [5]. The SDK or the underlying infrastructure often handles the normalization of these hosts, but explicitly providing the ingestion URL is the recommended practice [1][2].

Citations:


🌐 Web query:

PostHog React ui_host parameter difference from API host

💡 Result:

In PostHog, api_host and ui_host serve distinct purposes, particularly when you are using a reverse proxy to avoid ad blockers [1][2]. api_host This parameter defines the URL where the PostHog SDK sends data, such as captured events, session recordings, and feature flag evaluation requests [1][3]. If you are using a reverse proxy, you set api_host to the URL of your proxy (e.g., https://e.yourdomain.com or a relative path like /ingest) [2][3]. ui_host This parameter specifies the URL of the actual PostHog application (e.g., https://us.posthog.com or https://eu.posthog.com) [1][2]. It is required when using a proxy to ensure that features that link back to the PostHog dashboard—such as the Toolbar and the session recording player—function correctly [1][2]. If you do not set ui_host while using a proxy, the SDK may be unable to properly route these integrated features, causing them to break [2][4]. In standard configurations where you are not using a proxy, you typically only need to set api_host [1].

Citations:


Use separate environment variables for PostHog ingestion and UI endpoints.

The Node SDK's host (line 12–13) and the React provider's ui_host serve different purposes: the Node SDK's host should point to the ingestion endpoint (e.g., https://eu.i.posthog.com), while the React provider's ui_host should point to the PostHog dashboard (e.g., https://eu.posthog.com). Currently both use VITE_PUBLIC_POSTHOG_HOST, which can misroute server telemetry if the ingestion region differs from the dashboard region.

Introduce VITE_PUBLIC_POSTHOG_API_HOST for server-side ingestion in posthog-server.ts and use a separate VITE_PUBLIC_POSTHOG_UI_HOST in __root.tsx for the client provider's ui_host.

Suggested patch (server side)
 export function getPostHogClient() {
   if (!posthogClient) {
+    const token =
+      process.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN ??
+      import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN;
+    const ingestionHost =
+      process.env.VITE_PUBLIC_POSTHOG_API_HOST ??
+      import.meta.env.VITE_PUBLIC_POSTHOG_API_HOST ??
+      "https://eu.i.posthog.com";
+
+    if (!token) {
+      throw new Error("Missing VITE_PUBLIC_POSTHOG_PROJECT_TOKEN");
+    }
+
     posthogClient = new PostHog(
-      process.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN ||
-        import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN!,
+      token,
       {
-        host:
-          process.env.VITE_PUBLIC_POSTHOG_HOST ||
-          import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
+        host: ingestionHost,
         flushAt: 1,
         flushInterval: 0,
       },
     );
   }
📝 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
process.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN ||
import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN!,
{
host:
process.env.VITE_PUBLIC_POSTHOG_HOST ||
import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
export function getPostHogClient() {
if (!posthogClient) {
const token =
process.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN ??
import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN;
const ingestionHost =
process.env.VITE_PUBLIC_POSTHOG_API_HOST ??
import.meta.env.VITE_PUBLIC_POSTHOG_API_HOST ??
"https://eu.i.posthog.com";
if (!token) {
throw new Error("Missing VITE_PUBLIC_POSTHOG_PROJECT_TOKEN");
}
posthogClient = new PostHog(
token,
{
host: ingestionHost,
flushAt: 1,
flushInterval: 0,
},
);
}
🤖 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 `@web/src/utils/posthog-server.ts` around lines 8 - 13, Replace the `host`
configuration in the PostHog client initialization (lines 12-13 in
posthog-server.ts) to use a separate environment variable
VITE_PUBLIC_POSTHOG_API_HOST instead of VITE_PUBLIC_POSTHOG_HOST. This ensures
server-side ingestion uses the correct ingestion endpoint while keeping the
dashboard endpoint separate for client-side usage. Update the fallback chain to
check process.env.VITE_PUBLIC_POSTHOG_API_HOST first, then
import.meta.env.VITE_PUBLIC_POSTHOG_API_HOST.

Comment thread web/vite.config.ts
Comment on lines +23 to +40
"^/api/v1/static": {
target: "https://eu-assets.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, ""),
secure: false,
},
"^/api/v1/array": {
target: "https://eu-assets.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, ""),
secure: false,
},
"^/api/v1": {
target: "https://eu.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, ""),
secure: false,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the vite.config.ts file
find . -name "vite.config.ts" -type f

Repository: RMIT-BDSL/ExemplAI

Length of output: 177


🏁 Script executed:

# Get the full context of the vite.config.ts file
cat -n web/vite.config.ts

Repository: RMIT-BDSL/ExemplAI

Length of output: 1582


🌐 Web query:

vite proxy secure false certificate validation HTTPS

💡 Result:

In Vite, when configuring a proxy to an HTTPS target that uses a self-signed or invalid certificate, you may encounter errors such as UNABLE_TO_VERIFY_LEAF_SIGNATURE or self-signed certificate [1][2]. To resolve this, you can set the secure option to false within your proxy configuration in vite.config.js [3][1]. By default, Vite's proxy (which extends http-proxy) validates SSL certificates [4][1]. Setting secure: false disables this certificate validation [1][2]. Example configuration: // vite.config.js export default defineConfig({ server: { proxy: { '/api': { target: 'https://your-target-domain.com', changeOrigin: true, secure: false, // Disables SSL certificate validation }, }, }, }); Important Security Note: Setting secure: false should only be used in local development environments [1]. Never use this configuration in production, as it exposes the connection to man-in-the-middle attacks by failing to verify the identity of the server you are connecting to [1]. For environments where security matters, ensure your backend uses a trusted certificate or configure your local environment to trust your custom CA/certificate [1].

Citations:


Remove secure: false from PostHog proxy configurations.

Lines 27, 33, and 39 disable TLS certificate validation for HTTPS upstreams to PostHog services. PostHog's certificates are valid and publicly trusted; there is no reason to skip validation. Certificate verification should remain enabled even in development.

Suggested patch
       "^/api/v1/static": {
         target: "https://eu-assets.i.posthog.com",
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/api\/v1/, ""),
-        secure: false,
       },
       "^/api/v1/array": {
         target: "https://eu-assets.i.posthog.com",
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/api\/v1/, ""),
-        secure: false,
       },
       "^/api/v1": {
         target: "https://eu.i.posthog.com",
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/api\/v1/, ""),
-        secure: false,
       },
📝 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
"^/api/v1/static": {
target: "https://eu-assets.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, ""),
secure: false,
},
"^/api/v1/array": {
target: "https://eu-assets.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, ""),
secure: false,
},
"^/api/v1": {
target: "https://eu.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, ""),
secure: false,
},
"^/api/v1/static": {
target: "https://eu-assets.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, ""),
},
"^/api/v1/array": {
target: "https://eu-assets.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, ""),
},
"^/api/v1": {
target: "https://eu.i.posthog.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/v1/, ""),
},
🤖 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 `@web/vite.config.ts` around lines 23 - 40, Remove the `secure: false` property
from all three proxy server configurations in the vite.config.ts file.
Specifically, delete `secure: false` from the "^/api/v1/static" proxy
configuration, the "^/api/v1/array" proxy configuration, and the "^/api/v1"
proxy configuration. This will re-enable TLS certificate validation for HTTPS
connections to PostHog services, which is the secure default behavior.

@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: 5

🤖 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 `@server/.env.example`:
- Around line 5-12: The environment variables in the .env.example file are not
in alphabetical order. Move the SENTRY_DSN variable (currently at the beginning)
to come after all the RAPIDAPI_* variables (IS_RAPIDAPI, RAPIDAPI_KEY,
RAPIDAPI_HOST) to maintain proper alphabetical ordering. Ensure that SENTRY_DSN
and its associated comment are positioned after the RAPIDAPI_HOST variable and
its comment, so that the file follows alphabetical ordering as expected by
dotenv-linter.

In `@server/main.py`:
- Around line 111-125: Remove or sanitize the logging of raw execution payloads
in the execute_code function to prevent exposure of sensitive data.
Specifically, replace the log.info(output) call to avoid logging raw program
output, stderr, or compilation details. Additionally, replace the log.error call
that logs e.response.text in the HTTPStatusError handler with a sanitized log
message that does not expose upstream execution internals or raw response
payloads. Ensure that error details returned in HTTPException responses also
remain generic and do not expose sensitive execution details to clients.
- Around line 95-103: The code silently skips RapidAPI authentication headers
when credentials are missing, causing downstream failures without clear error
messaging. Add a validation check that when is_rapidapi is True, if
is_rapidapiconfig_valid is False (meaning RAPIDAPI_KEY or RAPIDAPI_HOST are
empty or missing), immediately return a 500 error response with a clear
configuration error message indicating which RapidAPI credentials are missing,
rather than proceeding with the header setup logic. This ensures the application
fails fast with explicit feedback instead of sending requests that are destined
to fail.
- Around line 113-135: Add exception chaining to three HTTPException raises in
the exception handlers (in the blocks for httpx.TimeoutException,
httpx.HTTPStatusError, and httpx.RequestError) by appending `from e` to each
raise statement to preserve the original exception chain per B904 violations.
Additionally, narrow the bare `except Exception:` block (which handles JSON
parsing errors in the HTTPStatusError handler) to `except ValueError:` since
JSON parsing errors specifically raise ValueError subclasses, resolving the
BLE001 violation.
- Line 8: The async version of the judge0_execution function (the one decorated
with the rate limiter) is missing the code.execution metric emission that exists
in the earlier judge0_execution definition. Since the async version overwrites
the first definition, it becomes the active handler but never records execution
events. Add the metrics.incr call for the code.execution counter to the async
judge0_execution function to ensure execution events are properly tracked by
Sentry when the /execute endpoint is called.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 51e764e3-11b3-420b-b3c2-fc8e8b545ac4

📥 Commits

Reviewing files that changed from the base of the PR and between f3e107b and 544b568.

⛔ Files ignored due to path filters (1)
  • server/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • server/.env.example
  • server/main.py
  • server/pyproject.toml
✅ Files skipped from review due to trivial changes (1)
  • server/pyproject.toml

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

🤖 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 `@server/.env.example`:
- Around line 5-12: The environment variables in the .env.example file are not
in alphabetical order. Move the SENTRY_DSN variable (currently at the beginning)
to come after all the RAPIDAPI_* variables (IS_RAPIDAPI, RAPIDAPI_KEY,
RAPIDAPI_HOST) to maintain proper alphabetical ordering. Ensure that SENTRY_DSN
and its associated comment are positioned after the RAPIDAPI_HOST variable and
its comment, so that the file follows alphabetical ordering as expected by
dotenv-linter.

In `@server/main.py`:
- Around line 111-125: Remove or sanitize the logging of raw execution payloads
in the execute_code function to prevent exposure of sensitive data.
Specifically, replace the log.info(output) call to avoid logging raw program
output, stderr, or compilation details. Additionally, replace the log.error call
that logs e.response.text in the HTTPStatusError handler with a sanitized log
message that does not expose upstream execution internals or raw response
payloads. Ensure that error details returned in HTTPException responses also
remain generic and do not expose sensitive execution details to clients.
- Around line 95-103: The code silently skips RapidAPI authentication headers
when credentials are missing, causing downstream failures without clear error
messaging. Add a validation check that when is_rapidapi is True, if
is_rapidapiconfig_valid is False (meaning RAPIDAPI_KEY or RAPIDAPI_HOST are
empty or missing), immediately return a 500 error response with a clear
configuration error message indicating which RapidAPI credentials are missing,
rather than proceeding with the header setup logic. This ensures the application
fails fast with explicit feedback instead of sending requests that are destined
to fail.
- Around line 113-135: Add exception chaining to three HTTPException raises in
the exception handlers (in the blocks for httpx.TimeoutException,
httpx.HTTPStatusError, and httpx.RequestError) by appending `from e` to each
raise statement to preserve the original exception chain per B904 violations.
Additionally, narrow the bare `except Exception:` block (which handles JSON
parsing errors in the HTTPStatusError handler) to `except ValueError:` since
JSON parsing errors specifically raise ValueError subclasses, resolving the
BLE001 violation.
- Line 8: The async version of the judge0_execution function (the one decorated
with the rate limiter) is missing the code.execution metric emission that exists
in the earlier judge0_execution definition. Since the async version overwrites
the first definition, it becomes the active handler but never records execution
events. Add the metrics.incr call for the code.execution counter to the async
judge0_execution function to ensure execution events are properly tracked by
Sentry when the /execute endpoint is called.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 51e764e3-11b3-420b-b3c2-fc8e8b545ac4

📥 Commits

Reviewing files that changed from the base of the PR and between f3e107b and 544b568.

⛔ Files ignored due to path filters (1)
  • server/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • server/.env.example
  • server/main.py
  • server/pyproject.toml
✅ Files skipped from review due to trivial changes (1)
  • server/pyproject.toml
🛑 Comments failed to post (5)
server/.env.example (1)

5-12: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Restore alphabetical ordering of environment variables.

The insertion of SENTRY_DSN at line 5 breaks the alphabetical ordering of the environment file. According to dotenv-linter, SENTRY_DSN (starting with 'S') should come after the RAPIDAPI_* variables (starting with 'R'). This ordering is important for maintainability and may be enforced by CI/linting tools.

🔧 Proposed fix: Reorder environment variables alphabetically
---- unchanged lines 1-4 not shown ----

-SENTRY_DSN=
 # For who uses RapidAPI, also configure the following:
 # determine whether or not to use rapidapi (case-sensitive - True/False)
 IS_RAPIDAPI=
 RAPIDAPI_KEY=

 # RapidAPI endpoint should only contain the domain name - excluding the http:// or alike (for example, google.com instead of https://google.com).
 RAPIDAPI_HOST=
+
+SENTRY_DSN=
📝 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.

# For who uses RapidAPI, also configure the following:
# determine whether or not to use rapidapi (case-sensitive - True/False)
IS_RAPIDAPI=
RAPIDAPI_KEY=

# RapidAPI endpoint should only contain the domain name - excluding the http:// or alike (for example, google.com instead of https://google.com).
RAPIDAPI_HOST=

SENTRY_DSN=
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 8-8: [UnorderedKey] The IS_RAPIDAPI key should go before the SENTRY_DSN key

(UnorderedKey)


[warning] 9-9: [UnorderedKey] The RAPIDAPI_KEY key should go before the SENTRY_DSN key

(UnorderedKey)

🤖 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 `@server/.env.example` around lines 5 - 12, The environment variables in the
.env.example file are not in alphabetical order. Move the SENTRY_DSN variable
(currently at the beginning) to come after all the RAPIDAPI_* variables
(IS_RAPIDAPI, RAPIDAPI_KEY, RAPIDAPI_HOST) to maintain proper alphabetical
ordering. Ensure that SENTRY_DSN and its associated comment are positioned after
the RAPIDAPI_HOST variable and its comment, so that the file follows
alphabetical ordering as expected by dotenv-linter.

Source: Linters/SAST tools

server/main.py (4)

8-8: 🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Expect: one counter emission inside judge0_execution, not just the import.
rg -n -C4 'def judge0_execution|metrics\.count\("code\.execution"' server/main.py

Repository: RMIT-BDSL/ExemplAI

Length of output: 687


🏁 Script executed:

cat -n server/main.py | sed -n '60,90p'

Repository: RMIT-BDSL/ExemplAI

Length of output: 1398


Add the code.execution metric to the active /execute handler.

The import at line 8 is present, but there are two conflicting judge0_execution function definitions: the first at line 68 includes the metric, but the second at line 74 (the async version with the rate limiter) overwrites it and does not emit the counter. The active handler never records execution events.

Proposed fix
`@limiter.limit`("10/minute")
async def judge0_execution(student_code: StudentCode, request: Request):
+    metrics.count("code.execution", 1)
     # send the code to judge0
🤖 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 `@server/main.py` at line 8, The async version of the judge0_execution function
(the one decorated with the rate limiter) is missing the code.execution metric
emission that exists in the earlier judge0_execution definition. Since the async
version overwrites the first definition, it becomes the active handler but never
records execution events. Add the metrics.incr call for the code.execution
counter to the async judge0_execution function to ensure execution events are
properly tracked by Sentry when the /execute endpoint is called.

95-103: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fail fast when RapidAPI mode is missing credentials.

With IS_RAPIDAPI=True, blank RAPIDAPI_KEY or RAPIDAPI_HOST silently skips auth headers and sends a doomed upstream request. Return a clear 500 configuration error instead.

🛠️ Proposed fix
     # support for rapidapi and that both string is not empty
-    is_rapidapiconfig_valid = os.getenv('RAPIDAPI_KEY', '').strip() and os.getenv('RAPIDAPI_HOST', '').strip()
-    if is_rapidapi and is_rapidapiconfig_valid:
-        headers['X-RapidAPI-Key'] = os.getenv('RAPIDAPI_KEY', '')
+    rapidapi_key = os.getenv('RAPIDAPI_KEY', '').strip()
+    rapidapi_host = os.getenv('RAPIDAPI_HOST', '').strip()
+    if is_rapidapi:
+        if not rapidapi_key or not rapidapi_host:
+            raise HTTPException(
+                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+                detail="RAPIDAPI_KEY and RAPIDAPI_HOST must be configured when IS_RAPIDAPI=True."
+            )
+        headers['X-RapidAPI-Key'] = rapidapi_key
         # Clean protocol scheme from Host if present (e.g. 'https://host' -> 'host')
-        host = os.getenv('RAPIDAPI_HOST', '')
+        host = rapidapi_host
         if "://" in host:
             host = host.split("://")[-1]
         headers['X-RapidAPI-Host'] = host
🤖 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 `@server/main.py` around lines 95 - 103, The code silently skips RapidAPI
authentication headers when credentials are missing, causing downstream failures
without clear error messaging. Add a validation check that when is_rapidapi is
True, if is_rapidapiconfig_valid is False (meaning RAPIDAPI_KEY or RAPIDAPI_HOST
are empty or missing), immediately return a 500 error response with a clear
configuration error message indicating which RapidAPI credentials are missing,
rather than proceeding with the header setup logic. This ensures the application
fails fast with explicit feedback instead of sending requests that are destined
to fail.

111-125: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid logging or returning raw execution payloads.

output and e.response.text can contain student program output, stderr, compile details, or upstream internals. Keep logs and API errors sanitized.

🔒 Proposed fix
             response.raise_for_status()
             output = response.json()
-            log.info(output)
+            log.info("Judge0 execution completed")
             return output
@@
     except httpx.HTTPStatusError as e:
-        log.error(f"Judge0 error response {e.response.status_code}: {e.response.text}")
+        log.error("Judge0 error response %s", e.response.status_code)
         try:
             err_data = e.response.json()
             detail = err_data.get("message") or err_data.get("error") or str(e)
         except Exception:
-            detail = f"Execution service returned error: {e.response.text}"
+            detail = "Execution service returned an error."
📝 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.

            log.info("Judge0 execution completed")
            return output
    except httpx.TimeoutException as e:
        log.error(f"Judge0 request timed out: {e}")
        raise HTTPException(
            status_code=status.HTTP_504_GATEWAY_TIMEOUT,
            detail="Code execution request timed out."
        )
    except httpx.HTTPStatusError as e:
        log.error("Judge0 error response %s", e.response.status_code)
        try:
            err_data = e.response.json()
            detail = err_data.get("message") or err_data.get("error") or str(e)
        except Exception:
            detail = "Execution service returned an error."
🧰 Tools
🪛 Ruff (0.15.18)

[warning] 115-118: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 124-124: Do not catch blind exception: Exception

(BLE001)

🤖 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 `@server/main.py` around lines 111 - 125, Remove or sanitize the logging of raw
execution payloads in the execute_code function to prevent exposure of sensitive
data. Specifically, replace the log.info(output) call to avoid logging raw
program output, stderr, or compilation details. Additionally, replace the
log.error call that logs e.response.text in the HTTPStatusError handler with a
sanitized log message that does not expose upstream execution internals or raw
response payloads. Ensure that error details returned in HTTPException responses
also remain generic and do not expose sensitive execution details to clients.

113-135: 📐 Maintainability & Code Quality | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate the file
find . -type f -name "main.py" | grep -E "server/main\.py"

Repository: RMIT-BDSL/ExemplAI

Length of output: 173


🏁 Script executed:

# Check the specific lines in the file
cat -n server/main.py | sed -n '110,140p'

Repository: RMIT-BDSL/ExemplAI

Length of output: 1554


🏁 Script executed:

# Run Ruff on the file to check for B904 and BLE001 violations
rg -U "ruff|pyproject|setup" . -t toml | head -5

Repository: RMIT-BDSL/ExemplAI

Length of output: 156


🏁 Script executed:

# Install and run Ruff to verify the violations
pip install ruff -q && ruff check server/main.py --select B904,BLE001 2>&1

Repository: RMIT-BDSL/ExemplAI

Length of output: 3163


Add exception chaining and narrow the JSON parse fallback to resolve Ruff violations.

Three B904 violations need exception chaining (from e), and the BLE001 violation at line 124 requires narrowing the bare except Exception to except ValueError since only JSON parsing errors should be caught there.

🧹 Proposed fix
     except httpx.TimeoutException as e:
         log.error(f"Judge0 request timed out: {e}")
         raise HTTPException(
             status_code=status.HTTP_504_GATEWAY_TIMEOUT,
             detail="Code execution request timed out."
-        )
+        ) from e
@@
-        except Exception:
+        except ValueError:
             detail = f"Execution service returned error: {e.response.text}"
         raise HTTPException(
             status_code=e.response.status_code,
             detail=detail
-        )
+        ) from e
@@
     except httpx.RequestError as e:
         log.error(f"Judge0 connection error: {e}")
         raise HTTPException(
             status_code=status.HTTP_502_BAD_GATEWAY,
             detail="Failed to connect to the code execution service."
-        )
+        ) from e
📝 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.

    except httpx.TimeoutException as e:
        log.error(f"Judge0 request timed out: {e}")
        raise HTTPException(
            status_code=status.HTTP_504_GATEWAY_TIMEOUT,
            detail="Code execution request timed out."
        ) from e
    except httpx.HTTPStatusError as e:
        log.error(f"Judge0 error response {e.response.status_code}: {e.response.text}")
        try:
            err_data = e.response.json()
            detail = err_data.get("message") or err_data.get("error") or str(e)
        except ValueError:
            detail = f"Execution service returned error: {e.response.text}"
        raise HTTPException(
            status_code=e.response.status_code,
            detail=detail
        ) from e
    except httpx.RequestError as e:
        log.error(f"Judge0 connection error: {e}")
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail="Failed to connect to the code execution service."
        ) from e
🧰 Tools
🪛 Ruff (0.15.18)

[warning] 115-118: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 124-124: Do not catch blind exception: Exception

(BLE001)


[warning] 126-129: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 132-135: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 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 `@server/main.py` around lines 113 - 135, Add exception chaining to three
HTTPException raises in the exception handlers (in the blocks for
httpx.TimeoutException, httpx.HTTPStatusError, and httpx.RequestError) by
appending `from e` to each raise statement to preserve the original exception
chain per B904 violations. Additionally, narrow the bare `except Exception:`
block (which handles JSON parsing errors in the HTTPStatusError handler) to
`except ValueError:` since JSON parsing errors specifically raise ValueError
subclasses, resolving the BLE001 violation.

Source: Linters/SAST tools

@millecodex millecodex merged commit bec42f0 into main Jun 23, 2026
1 check passed
@Ronaldo93 Ronaldo93 deleted the feat/observability-frontend branch June 23, 2026 11:35
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.

feat: frontend observability

2 participants