Skip to content

Posthog analytics#14

Merged
shubhamxdd merged 7 commits into
mainfrom
posthog-analytics
May 21, 2026
Merged

Posthog analytics#14
shubhamxdd merged 7 commits into
mainfrom
posthog-analytics

Conversation

@shubhamxdd
Copy link
Copy Markdown
Owner

@shubhamxdd shubhamxdd commented May 21, 2026

Summary by CodeRabbit

  • New Features

    • Integrated analytics to record key user actions (registration, login, dashboard/landing views, plan interactions, paper/resource lifecycle, question submissions, upgrade flows).
  • Chores

    • Added PostHog analytics dependencies and environment configuration entries for backend and frontend.
    • Initialized analytics on app startup and wired identification/tracking in major UI flows.

Review Change Stack

…of backend

  1. Authentication
   * user_registered: Captures when a new account is created.
       * Properties: email, method (password vs. google).
   * user_logged_in: Captures every session start.
       * Properties: method.

  2. Document Management (Resources)
   * resource_upload_started: Triggers as soon as a file hits our API.
       * Properties: filename, content_type, size_mb, type (notes/syllabus/etc).
   * resource_processed: Triggers in the Worker when OCR/Text extraction finishes.
       * Properties: num_pages, filename, type.
   * resource_processing_failed: Triggers if the AI fails to read the PDF.
       * Properties: error (the specific reason it failed).
   * resource_deleted: Triggers when a user removes a file.

  3. AI Tutor (Solver)
   * chat_session_created: Triggers when a new chat history starts.
   * ai_question_asked: Triggers for every message sent to the AI.
       * Properties: num_resources (how many files the AI is looking at), question_length (char count), delivery_mode.

  4. Exam Generator
   * paper_format_detected: Triggers when the AI successfully reads a syllabus and extracts a pattern (MCQs, Short Qs, etc).
   * paper_generation_started: Triggers when the generation job is queued.
   * paper_generated: Triggers in the Worker when the questions are ready.
       * Properties: num_questions.
   * paper_generation_failed: Triggers if the generator crashes.
       * Properties: error.
   * paper_pdf_downloaded: Triggers when the user clicks "Export to PDF".
       * Properties: mode (Study Guide vs. Question Paper).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Warning

Rate limit exceeded

@shubhamxdd has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 56 minutes and 12 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: ef8d6086-05e4-4768-a3c9-00e02b419417

📥 Commits

Reviewing files that changed from the base of the PR and between 7a5571b and 953ff9b.

📒 Files selected for processing (3)
  • backend/app/analytics.py
  • backend/requirements.txt
  • context/progress-tracker.md
📝 Walkthrough

Walkthrough

Integrates PostHog analytics: adds configuration and clients, initializes SDKs in backend and frontend, and instruments auth, paper/resource lifecycle, solver/chat, background tasks, and multiple frontend UI interactions with event captures.

Changes

PostHog Analytics Integration

Layer / File(s) Summary
Backend configuration and client initialization
.env.example, backend/app/config.py, backend/app/analytics.py, backend/app/main.py, backend/requirements.txt
PostHog settings (POSTHOG_API_KEY, POSTHOG_HOST) added; ph_client initialized with debug and conditional disable; ph_client imported into main; posthog dependency added (duplicate entry present).
Backend authentication router instrumentation
backend/app/routers/auth.py
Emits user_registered after registration persistence; emits user_logged_in on password and Google login flows; records registration/login with method and user identifiers.
Backend paper and resource lifecycle analytics
backend/app/routers/papers.py, backend/app/routers/resources.py
Emits paper_format_detected, paper_generation_started, paper_deleted, paper_pdf_downloaded; resource events include resource_upload_started, resource_deleted, resource_retry_extraction, and resource_stop_processing with metadata.
Backend async task event tracking
backend/app/workers/tasks.py
Extraction tasks emit resource_processed on PDF/TXT success and resource_processing_failed on exceptions; paper generation emits paper_generated on success and paper_generation_failed on failure.
Backend solver and chat instrumentation
backend/app/routers/solver.py
Emits chat_session_created on session creation and ai_question_asked after question/quota update with session and question metadata.
Frontend PostHog SDK and utilities
frontend/package.json, frontend/.env.example, frontend/src/lib/analytics.ts, frontend/src/main.tsx, frontend/src/App.tsx
Adds posthog-js dependency and VITE_POSTHOG_KEY; provides trackEvent, identifyUser, resetUser wrappers; conditionally initializes PostHog in main; App imports analytics utilities.
Frontend authentication tracking
frontend/src/pages/Login.tsx, frontend/src/pages/Register.tsx, frontend/src/App.tsx
Login identifies user and emits login_success; registration emits registration_success; Dashboard sync identifies user and emits dashboard_viewed.
Frontend page and user interaction analytics
frontend/src/pages/Landing.tsx, frontend/src/pages/Generator.tsx, frontend/src/pages/Resources.tsx, frontend/src/pages/Solver.tsx
Landing emits landing_page_viewed and plan_button_clicked; Generator emits paper_generation_clicked and limit_reached_modal_viewed; Resources emits upload success and limit modal events; Solver emits question_submitted and limit modal views.
Frontend upgrade modal instrumentation
frontend/src/components/UpgradeModal.tsx
"Upgrade Now" emits upgrade_button_clicked; "Maybe Later" emits upgrade_modal_dismissed before closing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • shubhamxdd/pyq-appl#3: Overlaps in resource-management endpoints and extraction task flows where analytics are added.
  • shubhamxdd/pyq-appl#4: Related modifications to solver/session routes that this PR instruments.
  • shubhamxdd/pyq-appl#8: Related paper generation and papers router code paths that receive analytics instrumentation.

Poem

🐰 A hop of events in morning light,

I tag each click and track each flight,
From login, upload, solver ask,
To paper builds and modal tasks—
A rabbit's metrics, stitched up tight.

🚥 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 'Posthog analytics' accurately reflects the main change—integrating PostHog analytics throughout the backend and frontend of 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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch posthog-analytics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (2)
backend/app/workers/tasks.py (2)

397-417: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

paper_generation_failed capture will AttributeError when paper is missing.

Same pattern as the resource failure path: paper_to_fail from scalar_one_or_none() on Line 398 can be None (paper deleted mid-generation is a real flow here — cancellation in Lines 332–343 transitions the paper out of pending/generating, and other endpoints can delete it). Line 414 then dereferences paper_to_fail.user_id and raises AttributeError, which the bare except: pass on Lines 420–421 silently swallows. Result: the failure event is never emitted exactly in the concurrent-mutation cases where it matters most.

🛡️ Proposed fix
                     await err_db.commit()
-                    
-                    ph_client.capture("paper_generation_failed", distinct_id=str(paper_to_fail.user_id), properties={
-                        "paper_id": str(paper_id),
-                        "error": str(e)
-                    })
+
+                    distinct_id = str(paper_to_fail.user_id) if paper_to_fail else str(paper_id)
+                    ph_client.capture("paper_generation_failed", distinct_id=distinct_id, properties={
+                        "paper_id": str(paper_id),
+                        "error": str(e)
+                    })

Separately (out of scope for this PR but worth tracking): replacing the bare except: pass on Lines 420–421 with at least a logger.exception(...) would prevent silently losing any future analytics errors here.

🤖 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 `@backend/app/workers/tasks.py` around lines 397 - 417, The capture call can
raise AttributeError if paper_to_fail is None; update the error-handling block
around res_upd/paper_to_fail and the ph_client.capture call (referencing
paper_to_fail, res_upd, ph_client.capture, err_db, Job and j) to guard against a
missing paper: after fetching paper_to_fail via res_upd.scalar_one_or_none(),
check if paper_to_fail is not None before accessing paper_to_fail.user_id and
paper_id in the properties; if it is None, supply a safe fallback distinct_id
(e.g. use j.user_id if the Job j exists or a stable string like "unknown") or
skip/adjust properties so ph_client.capture is always called with safe values;
keep the existing commit of err_db and preserve setting
j.status/j.error/j.completed_at as shown.

220-240: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

resource_processing_failed capture will AttributeError when resource is missing.

res_to_fail is the result of scalar_one_or_none() (Line 221) and may be None (e.g., the row was deleted concurrently, or it never existed for this resource_id). The subsequent capture on Line 237 accesses res_to_fail.user_id unconditionally, raising AttributeError. The surrounding broad except on Line 243 then silently swallows it, so the failure event you want to track is lost precisely when something is wrong.

Guard the capture, and fall back to the input identifiers when no row is found so you still get a failure event:

🛡️ Proposed fix
                     await err_db.commit()
-                    
-                    ph_client.capture("resource_processing_failed", distinct_id=str(res_to_fail.user_id), properties={
-                        "resource_id": str(resource_id),
-                        "error": str(e)
-                    })
+
+                    distinct_id = str(res_to_fail.user_id) if res_to_fail else str(resource_id)
+                    ph_client.capture("resource_processing_failed", distinct_id=distinct_id, properties={
+                        "resource_id": str(resource_id),
+                        "error": str(e)
+                    })
🤖 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 `@backend/app/workers/tasks.py` around lines 220 - 240, The capture call can
raise AttributeError because res_to_fail may be None; update the block around
res_to_fail and ph_client.capture so you check if res_to_fail is truthy before
accessing res_to_fail.user_id, and when it is None fall back to using the input
resource_id and a safe distinct_id (e.g., str(resource_id) or None) in
ph_client.capture properties; keep the existing updates to the Job (j) and
commit logic (res_to_fail, job lookup, j.status/j.error/j.completed_at, await
err_db.commit()) but change the distinct_id and properties passed to
ph_client.capture to use res_to_fail.user_id only when res_to_fail exists,
otherwise use fallback identifiers to avoid AttributeError while still emitting
the failure event.
🧹 Nitpick comments (1)
backend/app/main.py (1)

5-5: ⚡ Quick win

Flush analytics on app shutdown.

At Line 5, importing ph_client initializes it, but there’s no explicit shutdown/flush hook. Add a FastAPI shutdown handler to call the PostHog client’s close/flush method so events aren’t dropped during restarts.

🤖 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 `@backend/app/main.py` at line 5, Importing ph_client in main.py initializes
the PostHog client but there’s no shutdown hook to flush events; add a FastAPI
shutdown handler (e.g., using `@app.on_event`("shutdown") or
app.add_event_handler("shutdown", ...)) in main.py that calls the PostHog
client's cleanup method (call ph_client.shutdown() or ph_client.flush() as
appropriate for your client version) to ensure events are flushed on app
shutdown; reference the existing ph_client import and the FastAPI app instance
when adding the handler and handle async vs sync API accordingly.
🤖 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 `@backend/app/analytics.py`:
- Line 16: Remove any partial key exposure in the print at module
initialization: stop printing settings.POSTHOG_API_KEY (even masked). Replace
the print f-string in backend/app/analytics.py that references
settings.POSTHOG_API_KEY with a generic initialization message (e.g., "✅
[POSTHOG] Initialized") or use the existing logger to log a non-secret
"initialized" message; ensure no part of settings.POSTHOG_API_KEY appears in
logs or stdout.
- Around line 5-9: Change the Posthog client instantiation so it does not enable
verbose SDK logging by default: set the Posthog debug argument to False and wire
it to a new settings flag (e.g. settings.POSTHOG_DEBUG) so debug can only be
enabled via explicit configuration; update the settings/config module to read
POSTHOG_DEBUG from env (defaulting to False) and then construct ph_client with
Posthog(settings.POSTHOG_API_KEY, host=settings.POSTHOG_HOST,
debug=settings.POSTHOG_DEBUG) so sensitive payloads are not logged unless
deliberately turned on.

In `@backend/app/routers/resources.py`:
- Around line 98-104: ph_client.capture("resource_upload_started", ...)
currently runs before the outer try/except that performs cleanup via
storage_service.delete_file(object_name), so if analytics fails after
storage_service.upload_file(...) succeeds you can leave orphaned files; move the
ph_client.capture call into the guarded transactional/cleanup section (the same
try/except that calls storage_service.delete_file) or wrap the capture in its
own local try/except that catches and logs errors but does not re-raise, making
the analytics call best-effort while ensuring
storage_service.delete_file(object_name) always runs.

In `@backend/app/routers/solver.py`:
- Line 251: The SSE response is using an invalid MIME type; update the
StreamingResponse call in solver.py (the return that wraps event_generator()) to
use the correct SSE content type "text/event-stream" instead of
"text_event-stream", and revert any accompanying change or comment that labeled
the incorrect value as a correction so the code and any summary reflect the
proper SSE MIME type; ensure the symbol StreamingResponse(event_generator()) is
the one modified.

In `@backend/requirements.txt`:
- Around line 22-23: The requirements file contains a duplicated dependency
entry for the package "posthog"; remove the extra "posthog" line so only a
single entry remains and verify there are no other duplicate package names in
requirements.txt to avoid install conflicts.

In `@frontend/src/pages/Generator.tsx`:
- Line 152: The code currently sends raw user-entered `title` to analytics via
the trackEvent call in Generator.tsx; change it to send non-sensitive metadata
instead (e.g., titleLength: title.length, template or category identifiers,
boolean flags like isCustomTitle) and remove the raw `title` field; update the
callsite that invokes trackEvent('paper_generation_clicked', ...) so it passes a
metadata object (refer to the trackEvent usage and the local `title` variable)
rather than the free-form title string.
- Line 159: The analytics call may send a stale upgradeMessage because
setUpgradeMessage(...) is asynchronous; create the upgrade text in a local
variable inside the handler (e.g., const msg = "..."), call
setUpgradeMessage(msg), and then pass that same msg to
trackEvent('limit_reached_modal_viewed', { feature: 'generator', message: msg })
so both state and analytics use the exact same resolved string; update the code
paths in Generator.tsx where setUpgradeMessage and trackEvent are called to use
the local variable instead of reading upgradeMessage state.

In `@frontend/src/pages/Register.tsx`:
- Line 57: The analytics call trackEvent('registration_success', { email:
data.email }) is sending raw PII; remove the direct email from the payload and
replace it with a non-PII value (or omit payload entirely). Update the call in
Register.tsx (the trackEvent invocation for 'registration_success') to either
send no properties, send a generic flag like { created: true }, or send a
hashed/anonymized identifier (e.g., a one-way hash or userId) instead of
data.email to preserve privacy/compliance.

In `@frontend/src/pages/Resources.tsx`:
- Line 99: The call to trackEvent('limit_reached_modal_viewed', { feature:
'resources', message: upgradeMessage }) can capture a stale upgradeMessage
because React state updates (setUpgradeMessage) haven't occurred yet; fix by
creating a local const (e.g., const message = '<computed message>' or derived
value) before calling setUpgradeMessage, use that local message when calling
trackEvent and then call setUpgradeMessage(message) so trackEvent and state
setter use the same, up-to-date value; update references in the Resources
component around trackEvent, upgradeMessage, and setUpgradeMessage accordingly.

In `@frontend/src/pages/Solver.tsx`:
- Line 193: The analytics call may read the stale upgradeMessage state because
setUpgradeMessage(...) is async; compute the upgrade message into a local
variable (e.g., message) inside the Solver component where you build the string,
call setUpgradeMessage(message), then call
trackEvent('limit_reached_modal_viewed', { feature: 'solver', message }) so
trackEvent receives the exact message shown in the modal; update references
around setUpgradeMessage and the trackEvent call accordingly.

---

Outside diff comments:
In `@backend/app/workers/tasks.py`:
- Around line 397-417: The capture call can raise AttributeError if
paper_to_fail is None; update the error-handling block around
res_upd/paper_to_fail and the ph_client.capture call (referencing paper_to_fail,
res_upd, ph_client.capture, err_db, Job and j) to guard against a missing paper:
after fetching paper_to_fail via res_upd.scalar_one_or_none(), check if
paper_to_fail is not None before accessing paper_to_fail.user_id and paper_id in
the properties; if it is None, supply a safe fallback distinct_id (e.g. use
j.user_id if the Job j exists or a stable string like "unknown") or skip/adjust
properties so ph_client.capture is always called with safe values; keep the
existing commit of err_db and preserve setting j.status/j.error/j.completed_at
as shown.
- Around line 220-240: The capture call can raise AttributeError because
res_to_fail may be None; update the block around res_to_fail and
ph_client.capture so you check if res_to_fail is truthy before accessing
res_to_fail.user_id, and when it is None fall back to using the input
resource_id and a safe distinct_id (e.g., str(resource_id) or None) in
ph_client.capture properties; keep the existing updates to the Job (j) and
commit logic (res_to_fail, job lookup, j.status/j.error/j.completed_at, await
err_db.commit()) but change the distinct_id and properties passed to
ph_client.capture to use res_to_fail.user_id only when res_to_fail exists,
otherwise use fallback identifiers to avoid AttributeError while still emitting
the failure event.

---

Nitpick comments:
In `@backend/app/main.py`:
- Line 5: Importing ph_client in main.py initializes the PostHog client but
there’s no shutdown hook to flush events; add a FastAPI shutdown handler (e.g.,
using `@app.on_event`("shutdown") or app.add_event_handler("shutdown", ...)) in
main.py that calls the PostHog client's cleanup method (call
ph_client.shutdown() or ph_client.flush() as appropriate for your client
version) to ensure events are flushed on app shutdown; reference the existing
ph_client import and the FastAPI app instance when adding the handler and handle
async vs sync API accordingly.
🪄 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: 9cb22d19-b5ec-4066-b85e-13bb2d7bb3d5

📥 Commits

Reviewing files that changed from the base of the PR and between 19b8ead and 3851a40.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • .env.example
  • backend/app/analytics.py
  • backend/app/config.py
  • backend/app/main.py
  • backend/app/routers/auth.py
  • backend/app/routers/papers.py
  • backend/app/routers/resources.py
  • backend/app/routers/solver.py
  • backend/app/workers/tasks.py
  • backend/requirements.txt
  • frontend/.env.example
  • frontend/package.json
  • frontend/src/App.tsx
  • frontend/src/components/UpgradeModal.tsx
  • frontend/src/lib/analytics.ts
  • frontend/src/main.tsx
  • frontend/src/pages/Generator.tsx
  • frontend/src/pages/Landing.tsx
  • frontend/src/pages/Login.tsx
  • frontend/src/pages/Register.tsx
  • frontend/src/pages/Resources.tsx
  • frontend/src/pages/Solver.tsx

Comment thread backend/app/analytics.py
Comment on lines +5 to +9
ph_client = Posthog(
settings.POSTHOG_API_KEY,
host=settings.POSTHOG_HOST,
debug=True # Helpful for debugging delivery issues
)
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
set -euo pipefail

# Inspect the referenced file and surrounding context
sed -n '1,120p' backend/app/analytics.py | nl -ba

# Search for other Posthog initialization / debug flags
rg -n "Posthog\\(|debug\\s*=\\s*True|POSTHOG" backend -S || true

# Show settings related to Posthog if present
rg -n "POSTHOG_(API_KEY|HOST)" -S . || true
rg -n "class Settings|settings\\." backend -S || true

Repository: shubhamxdd/pyq-appl

Length of output: 106


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the referenced file with line numbers (nl not available)
awk '{printf "%6d\t%s\n", NR, $0}' backend/app/analytics.py | sed -n '1,160p'

# Search for other Posthog initialization / debug flags
rg -n "Posthog\\(" backend -S || true
rg -n "debug\\s*=\\s*True|debug\\s*=\\s*false|POSTHOG" backend -S || true

# Search for settings definitions / env flags around POSTHOG
rg -n "POSTHOG_(API_KEY|HOST|DEBUG)|posthog" backend -S -i || true
rg -n "class Settings|BaseSettings|settings\\s*=" backend -S || true

Repository: shubhamxdd/pyq-appl

Length of output: 2374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

awk '{printf "%6d\t%s\n", NR, $0}' backend/app/config.py | sed -n '1,140p'

Repository: shubhamxdd/pyq-appl

Length of output: 1449


🏁 Script executed:

#!/bin/bash
set -euo pipefail

awk '{printf "%6d\t%s\n", NR, $0}' backend/app/config.py | sed -n '1,140p'

Repository: shubhamxdd/pyq-appl

Length of output: 1449


🌐 Web query:

PostHog Python Posthog debug parameter what does debug=True do logs payloads

💡 Result:

In the PostHog Python SDK, setting debug=True enables verbose logging, which helps troubleshoot issues with event capturing, feature flags, or survey rendering [1]. When enabled, the SDK changes the logging level of the posthog logger to DEBUG [2]. This causes the library to output internal operational logs to the standard logging output [1][2]. Specifically, enabling debug=True performs the following actions: - It configures the logger by calling logging.basicConfig() [2]. - It sets the log level for the posthog logger to logging.DEBUG (instead of the default logging.WARNING) [2]. - This allows you to see detailed information about the SDK's internal processes [1], which can include information regarding the processing and sending of payloads, helping you verify if events or configurations are being handled as expected [1]. You can enable this mode during client initialization or by setting the attribute directly on the client instance: python # During initialization from posthog import Posthog posthog = Posthog('<ph_project_token>', debug=True) # Or by setting the attribute posthog.debug = True If you are not seeing expected data appear in your PostHog instance, this mode is the recommended way to inspect the SDK's activity and identify why events might not be reaching the server [1].

Citations:


Disable PostHog debug mode by default.

debug=True in backend/app/analytics.py turns on the PostHog SDK’s verbose logging (sets the posthog logger to DEBUG), which can cause sensitive event/payload processing details to end up in server logs. Default debug to False and gate it behind an explicit settings/env flag (the current backend/app/config.py has POSTHOG_API_KEY/POSTHOG_HOST only, with no debug toggle).

ph_client = Posthog(
    settings.POSTHOG_API_KEY,
    host=settings.POSTHOG_HOST,
    debug=True
)
🤖 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 `@backend/app/analytics.py` around lines 5 - 9, Change the Posthog client
instantiation so it does not enable verbose SDK logging by default: set the
Posthog debug argument to False and wire it to a new settings flag (e.g.
settings.POSTHOG_DEBUG) so debug can only be enabled via explicit configuration;
update the settings/config module to read POSTHOG_DEBUG from env (defaulting to
False) and then construct ph_client with Posthog(settings.POSTHOG_API_KEY,
host=settings.POSTHOG_HOST, debug=settings.POSTHOG_DEBUG) so sensitive payloads
are not logged unless deliberately turned on.

Comment thread backend/app/analytics.py Outdated
Comment on lines +98 to +104
ph_client.capture("resource_upload_started", distinct_id=str(current_user.id), properties={
"filename": file.filename,
"content_type": file.content_type,
"size_mb": total_size / (1024 * 1024),
"type": type
})

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
set -euo pipefail

# Show the relevant portion of the file with line numbers
sed -n '70,140p' backend/app/routers/resources.py | nl -ba -w3 -s': '

# Also find where the upload/try/cleanup block is defined in this file
rg -n "try:|except:|finally:|cleanup|rollback|delete|remove|capture\\(" backend/app/routers/resources.py

# Show the exact ph_client usage context (a bit wider)
rg -n "ph_client\\.capture" -n backend/app/routers/resources.py

Repository: shubhamxdd/pyq-appl

Length of output: 106


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== resources.py (lines ~1-220) with line numbers) ==="
# Use cat -n instead of nl (nl is missing in the sandbox)
sed -n '1,220p' backend/app/routers/resources.py | cat -n -w3 -s': '

echo "=== Search for upload/try/cleanup/rollback and capture usage ==="
rg -n "ph_client\\.capture|try:|except:|finally:|cleanup|rollback|delete|remove|filesystem|storage|upload" backend/app/routers/resources.py

Repository: shubhamxdd/pyq-appl

Length of output: 202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== resources.py: lines 60-140 (with line numbers) ==="
sed -n '60,140p' backend/app/routers/resources.py | cat -n

echo "=== Exact ph_client.capture call contexts ==="
rg -n "ph_client\.capture" backend/app/routers/resources.py

echo "=== Surrounding try/except/finally around capture (context) ==="
# Print a window around the capture line(s)
CAPTURE_LINE="$(rg -n "ph_client\.capture" backend/app/routers/resources.py | head -n1 | cut -d: -f1 || true)"
if [ -n "${CAPTURE_LINE}" ]; then
  START=$((CAPTURE_LINE-30))
  END=$((CAPTURE_LINE+30))
  echo "CAPTURE_LINE=${CAPTURE_LINE}; showing ${START}-${END}"
  sed -n "${START},${END}p" backend/app/routers/resources.py | cat -n
fi

Repository: shubhamxdd/pyq-appl

Length of output: 6809


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== resources.py: lines 90-170 (capture + surrounding try/except) ==="
sed -n '90,170p' backend/app/routers/resources.py | cat -n

echo "=== resources.py: find storage cleanup/delete/remove calls in this area ==="
rg -n "delete|remove|cleanup|rollback|finally" backend/app/routers/resources.py | head -n 80

Repository: shubhamxdd/pyq-appl

Length of output: 4139


Move ph_client.capture inside the transactional/cleanup block (best-effort)

ph_client.capture("resource_upload_started", ...) runs before the outer try/except that cleans up storage via storage_service.delete_file(object_name). If analytics throws after storage_service.upload_file(...) succeeds, the function will exit without that cleanup, leaving orphaned files in storage. Wrap the capture in the guarded section (or add a local try/except around it) and make it best-effort.

🤖 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 `@backend/app/routers/resources.py` around lines 98 - 104,
ph_client.capture("resource_upload_started", ...) currently runs before the
outer try/except that performs cleanup via
storage_service.delete_file(object_name), so if analytics fails after
storage_service.upload_file(...) succeeds you can leave orphaned files; move the
ph_client.capture call into the guarded transactional/cleanup section (the same
try/except that calls storage_service.delete_file) or wrap the capture in its
own local try/except that catches and logs errors but does not re-raise, making
the analytics call best-effort while ensuring
storage_service.delete_file(object_name) always runs.

Comment thread backend/app/routers/solver.py Outdated
Comment thread backend/requirements.txt Outdated
Comment thread frontend/src/pages/Generator.tsx
Comment thread frontend/src/pages/Generator.tsx
Comment thread frontend/src/pages/Register.tsx
if (error.response?.status === 403) {
setUpgradeMessage(error.response?.data?.detail || "You've reached your resource storage limit.");
setIsUpgradeModalOpen(true);
trackEvent('limit_reached_modal_viewed', { feature: 'resources', message: upgradeMessage });
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

Fix stale upgradeMessage usage in limit tracking.

Line 99 can log an old message value because state hasn’t updated yet. Use a local message constant before calling setUpgradeMessage.

Suggested fix
     onError: (error: any) => {
       if (error.response?.status === 403) {
-        setUpgradeMessage(error.response?.data?.detail || "You've reached your resource storage limit.");
+        const message = error.response?.data?.detail || "You've reached your resource storage limit.";
+        setUpgradeMessage(message);
         setIsUpgradeModalOpen(true);
-        trackEvent('limit_reached_modal_viewed', { feature: 'resources', message: upgradeMessage });
+        trackEvent('limit_reached_modal_viewed', { feature: 'resources', message });
       } else {
📝 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
trackEvent('limit_reached_modal_viewed', { feature: 'resources', message: upgradeMessage });
onError: (error: any) => {
if (error.response?.status === 403) {
const message = error.response?.data?.detail || "You've reached your resource storage limit.";
setUpgradeMessage(message);
setIsUpgradeModalOpen(true);
trackEvent('limit_reached_modal_viewed', { feature: 'resources', message });
} else {
🤖 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 `@frontend/src/pages/Resources.tsx` at line 99, The call to
trackEvent('limit_reached_modal_viewed', { feature: 'resources', message:
upgradeMessage }) can capture a stale upgradeMessage because React state updates
(setUpgradeMessage) haven't occurred yet; fix by creating a local const (e.g.,
const message = '<computed message>' or derived value) before calling
setUpgradeMessage, use that local message when calling trackEvent and then call
setUpgradeMessage(message) so trackEvent and state setter use the same,
up-to-date value; update references in the Resources component around
trackEvent, upgradeMessage, and setUpgradeMessage accordingly.

const errData = await response.json();
setUpgradeMessage(errData.detail || "You've reached your question limit.");
setIsUpgradeModalOpen(true);
trackEvent('limit_reached_modal_viewed', { feature: 'solver', message: upgradeMessage });
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

Track the same computed message you show in the modal.

Line 193 reads upgradeMessage right after setUpgradeMessage(...), so analytics may receive the previous value. Use a local message variable.

Suggested fix
       if (response.status === 403) {
         const errData = await response.json();
-        setUpgradeMessage(errData.detail || "You've reached your question limit.");
+        const message = errData.detail || "You've reached your question limit.";
+        setUpgradeMessage(message);
         setIsUpgradeModalOpen(true);
-        trackEvent('limit_reached_modal_viewed', { feature: 'solver', message: upgradeMessage });
+        trackEvent('limit_reached_modal_viewed', { feature: 'solver', message });
         setMessages(prev => prev.slice(0, -1));
         setIsStreaming(false);
         return;
📝 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
trackEvent('limit_reached_modal_viewed', { feature: 'solver', message: upgradeMessage });
if (response.status === 403) {
const errData = await response.json();
const message = errData.detail || "You've reached your question limit.";
setUpgradeMessage(message);
setIsUpgradeModalOpen(true);
trackEvent('limit_reached_modal_viewed', { feature: 'solver', message });
setMessages(prev => prev.slice(0, -1));
setIsStreaming(false);
return;
🤖 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 `@frontend/src/pages/Solver.tsx` at line 193, The analytics call may read the
stale upgradeMessage state because setUpgradeMessage(...) is async; compute the
upgrade message into a local variable (e.g., message) inside the Solver
component where you build the string, call setUpgradeMessage(message), then call
trackEvent('limit_reached_modal_viewed', { feature: 'solver', message }) so
trackEvent receives the exact message shown in the modal; update references
around setUpgradeMessage and the trackEvent call accordingly.

shubhamxdd and others added 2 commits May 21, 2026 22:46
text_event-stream is not a valid MIME type. The SSE spec (and EventSource) requires text/event-stream; with an underscore and no /, browsers/proxies won't recognize the response as SSE, causing clients that expect Content-Type: text/event-stream to fail (and intermediaries may buffer the stream, defeating real-time delivery). The cohort summary describing this as a "correction" appears inverted — please revert.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@shubhamxdd shubhamxdd merged commit c9e1c1d into main May 21, 2026
1 check passed
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.

1 participant