Repository navigation
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughPostHog analytics is integrated into the Muolingo Expo app via a new ChangesApp PostHog Integration
Claude Skill: integration-expo
Sequence Diagram(s)sequenceDiagram
actor User
participant Screen as Expo Screen
participant PostHogProvider as PostHogProvider (_layout.tsx)
participant PostHogTS as lib/posthog.ts
participant PostHogSDK as posthog-react-native
User->>Screen: navigates / interacts
Screen->>PostHogProvider: pathname/params change via usePathname
PostHogProvider->>PostHogTS: posthog.screen(pathname, { previous_screen, ...params })
PostHogTS->>PostHogSDK: enqueue screen event
User->>Screen: taps sign-in / sign-up
Screen->>PostHogTS: posthog.capture("sign_in_submitted")
Screen->>PostHogTS: posthog.identify(email, { $set, $set_once })
Screen->>PostHogTS: posthog.capture("sign_in_completed")
PostHogTS->>PostHogSDK: flush batch to PostHog API
User->>Screen: taps sign-out
Screen->>PostHogTS: posthog.capture("user_signed_out")
Screen->>PostHogTS: posthog.reset()
PostHogTS->>PostHogSDK: reset distinct_id
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 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 @.claude/skills/integration-expo/references/identify-users.md:
- Around line 13-62: The markdown heading hierarchy is incorrect, jumping from
the implicit document heading directly to H3 (###) level headings for the
platform sections. Fix this by changing all five platform section headings from
three hashes to two hashes: change "### Web", "### Android", "### iOS", "###
React Native", and "### Dart" to use "##" instead of "###" to maintain proper
heading hierarchy and compliance with the markdown heading-increment rule.
- Around line 195-258: The markdown file has duplicate heading levels creating a
MD024 warning. The file contains two sets of "### iOS" and "### Android"
headings under the same parent section. To fix this, rename the first or second
occurrence of these duplicate headings to make them unique. For example, change
the first pair to "### iOS Implementation" and "### Android Implementation" or
rename the second pair similarly. Alternatively, elevate one complete pair of
iOS and Android sections to use "## " level headings if they represent distinct
top-level subsections within the deep links guidance section.
In @.claude/skills/integration-expo/references/react-native.md:
- Line 5: The opening sentence in the React Native integration reference is
missing the word "SDK" which makes it grammatically incomplete. Find the phrase
"Our React Native enables you to integrate" and add "SDK" between "React Native"
and "enables" so it reads "Our React Native SDK enables you to integrate PostHog
with your React Native project."
In @.claude/skills/integration-expo/SKILL.md:
- Around line 42-50: The SKILL.md file contains contradictory configuration
guidance for PostHog setup. Remove the duplicate statement about
posthog-react-native being the React Native SDK package name that appears on
both line 42 and line 46. Then address the contradiction between the
expo-constants approach (lines 43–44) and the react-native-config approach
(lines 47–48): since this is an Expo-focused skill file, either remove the
react-native-config guidance entirely (lines 47–48) if it is only applicable to
bare React Native projects, or clearly separate the two patterns with explicit
section headers (such as "For Expo:" and "For Bare React Native:") so developers
understand which approach applies to their project type. The codebase currently
implements the expo-constants pattern, so align the documentation with that
approach.
In @.env:
- Around line 1-3: Remove the actual credential values from the .env file.
Replace the concrete key values for EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY and
POSTHOG_PROJECT_TOKEN with placeholder strings (such as empty strings or example
values like pk_test_placeholder). Ensure the .env file is added to .gitignore to
prevent future commits of actual credentials. Note that simply adding to
.gitignore later will not remove these already-committed values from repository
history, so the values must be removed from this commit.
In `@app/_layout.tsx`:
- Around line 35-43: The PostHog configuration has automatic screen capture
enabled, which conflicts with Expo Router since the PostHog provider is placed
outside the navigation context. Locate the PostHog provider initialization code
and set the autocapture.captureScreens option to false. This allows the manual
posthog.screen() calls in the useEffect hook (lines 35-43) to handle screen
tracking without interference from automatic capture that isn't compatible with
React Navigation v7+.
- Around line 37-40: The posthog.screen() call in the analytics tracking block
spreads all params from useGlobalSearchParams() directly without filtering,
which can expose sensitive data like verification codes or reset tokens to
analytics. Create a helper function to filter out known sensitive parameters
(such as those containing tokens, codes, credentials, or verification-related
keys) and apply this filter to params before spreading them into the
posthog.screen() call. Only send non-sensitive, safe parameters to the analytics
service.
In `@app/`(tabs)/profile.tsx:
- Around line 15-20: The handleSignOut function needs error handling for the
signOut() call which can fail per Clerk documentation. Wrap the entire async
flow in a try/catch block. Move the posthog.reset() call to execute only after a
successful signOut() to prevent inconsistent state where the user remains signed
in but analytics are cleared. Additionally, add a call to
clearSelectedLanguage() within the try block for complete user state cleanup. In
the catch block, handle any errors appropriately (consider logging or showing an
error message to the user) and ensure navigation does not occur if sign-out
fails.
In `@components/VerificationModal.tsx`:
- Line 82: The posthog.capture call for "email_verification_code_resent" is
currently being emitted before awaiting the onResend() call, which means failed
resend attempts are incorrectly tracked as successful. Move the
posthog.capture("email_verification_code_resent", { email }) event to execute
only after the await onResend() completes successfully. Additionally, consider
adding a separate failure event (such as
"email_verification_code_resent_failed") in the catch block to track
unsuccessful resend attempts.
In `@posthog-setup-report.md`:
- Line 38: The identify() function is currently only called in sign-in.tsx and
sign-up.tsx during fresh authentication flows, but users restored from Clerk's
tokenCache on app boot are not being identified, leaving them under anonymous
distinct IDs. In the _layout.tsx file, locate the useEffect hook (around lines
45-63) and add an identify() call that triggers when isSignedIn becomes true,
particularly to handle the case where a user already has an authenticated
session restored from the token cache. This ensures all authenticated users—both
fresh and returning from token cache—are properly identified in PostHog rather
than only those completing a manual sign-in flow.
🪄 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 Plus
Run ID: 00d9e38c-d58d-4873-8e1d-ffed0f9745e5
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (23)
.claude/skills/integration-expo/.posthog-wizard.claude/skills/integration-expo/SKILL.md.claude/skills/integration-expo/references/1-begin.md.claude/skills/integration-expo/references/2-edit.md.claude/skills/integration-expo/references/3-revise.md.claude/skills/integration-expo/references/4-conclude.md.claude/skills/integration-expo/references/EXAMPLE.md.claude/skills/integration-expo/references/identify-users.md.claude/skills/integration-expo/references/react-native.md.env.gitignoreapp.config.jsapp/(auth)/sign-in.tsxapp/(auth)/sign-up.tsxapp/(tabs)/index.tsxapp/(tabs)/profile.tsxapp/_layout.tsxapp/language-selection.tsxapp/onboarding.tsxcomponents/VerificationModal.tsxlib/posthog.tspackage.jsonposthog-setup-report.md
| ### Web | ||
|
|
||
| ```javascript | ||
| posthog.identify( | ||
| 'distinct_id', // Replace 'distinct_id' with your user's unique identifier | ||
| { email: 'max@hedgehogmail.com', name: 'Max Hedgehog' } // optional: set additional person properties | ||
| ); | ||
| ``` | ||
|
|
||
| ### Android | ||
|
|
||
| ```kotlin | ||
| PostHog.identify( | ||
| distinctId = distinctID, // Replace 'distinctID' with your user's unique identifier | ||
| // optional: set additional person properties | ||
| userProperties = mapOf( | ||
| "name" to "Max Hedgehog", | ||
| "email" to "max@hedgehogmail.com" | ||
| ) | ||
| ) | ||
| ``` | ||
|
|
||
| ### iOS | ||
|
|
||
| ```swift | ||
| PostHogSDK.shared.identify("distinct_id", // Replace "distinct_id" with your user's unique identifier | ||
| userProperties: ["name": "Max Hedgehog", "email": "max@hedgehogmail.com"]) // optional: set additional person properties | ||
| ``` | ||
|
|
||
| ### React Native | ||
|
|
||
| ```jsx | ||
| posthog.identify('distinct_id', { // Replace "distinct_id" with your user's unique identifier | ||
| email: 'max@hedgehogmail.com', // optional: set additional person properties | ||
| name: 'Max Hedgehog' | ||
| }) | ||
| ``` | ||
|
|
||
| ### Dart | ||
|
|
||
| ```dart | ||
| await Posthog().identify( | ||
| userId: 'distinct_id', // Replace "distinct_id" with your user's unique identifier | ||
| userProperties: { | ||
| 'email': 'max@hedgehogmail.com', // optional: set additional person properties | ||
| 'name': 'Max Hedgehog', | ||
| }, | ||
| ); | ||
| ``` | ||
|
|
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix heading level increment: "### Web" should be "## Web".
The markdown structure jumps from implicit H1/H2 (from YAML frontmatter) to "### Web" at line 13. This violates the heading-increment rule (MD001): headings should only increase by one level at a time.
Change lines 13, 22, 35, 42, 51 from ### to ## to match the platform examples structure:
- Line 13:
## Web - Line 22:
## Android - Line 35:
## iOS - Line 42:
## React Native - Line 51:
## Dart
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 13-13: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3
(MD001, heading-increment)
🤖 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 @.claude/skills/integration-expo/references/identify-users.md around lines 13
- 62, The markdown heading hierarchy is incorrect, jumping from the implicit
document heading directly to H3 (###) level headings for the platform sections.
Fix this by changing all five platform section headings from three hashes to two
hashes: change "### Web", "### Android", "### iOS", "### React Native", and "###
Dart" to use "##" instead of "###" to maintain proper heading hierarchy and
compliance with the markdown heading-increment rule.
| ### iOS | ||
|
|
||
| ```swift | ||
| import PostHog | ||
| class DeepLinkIdentityManager { | ||
| static let shared = DeepLinkIdentityManager() | ||
| // MARK: - Deep Link Received | ||
| func handleDeepLink(_ url: URL, isAuthenticatedOnMobile: Bool) { | ||
| guard let webDistinctId = URLComponents(url: url, resolvingAgainstBaseURL: true)? | ||
| .queryItems?.first(where: { $0.name == "ph_distinct_id" })?.value else { | ||
| return | ||
| } | ||
| if isAuthenticatedOnMobile { | ||
| // The mobile app already knows the current user. | ||
| // Alias the incoming web distinct ID to that user. | ||
| PostHogSDK.shared.alias(webDistinctId) | ||
| } else { | ||
| // Reuse the web distinct ID until login on mobile. | ||
| PostHogSDK.shared.identify(webDistinctId) | ||
| } | ||
| } | ||
| // MARK: - Login/Signup | ||
| func handleLogin(canonicalUserId: String) { | ||
| // Switch from the web distinct ID (or a mobile anon ID) | ||
| // to your canonical user ID. | ||
| PostHogSDK.shared.identify(canonicalUserId) | ||
| // Set user properties, track signup event, etc. | ||
| } | ||
| func handleLogout() { | ||
| PostHogSDK.shared.reset() | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Android | ||
|
|
||
| ```kotlin | ||
| import android.net.Uri | ||
| import com.posthog.PostHog | ||
| object DeepLinkIdentityManager { | ||
| // Deep Link Received | ||
| fun handleDeepLink(uri: Uri, isAuthenticatedOnMobile: Boolean) { | ||
| val webDistinctId = uri.getQueryParameter("ph_distinct_id") ?: return | ||
| if (isAuthenticatedOnMobile) { | ||
| // The mobile app already knows the current user. | ||
| // Alias the incoming web distinct ID to that user. | ||
| PostHog.alias(webDistinctId) | ||
| } else { | ||
| // Reuse the web distinct ID until login on mobile. | ||
| PostHog.identify(webDistinctId) | ||
| } | ||
| } | ||
| // Login/Signup | ||
| fun handleLogin(canonicalUserId: String) { | ||
| // Switch from the web distinct ID (or a mobile anon ID) | ||
| // to your canonical user ID. | ||
| PostHog.identify(canonicalUserId) | ||
| // Set user properties, track signup event, etc. | ||
| } | ||
| fun handleLogout() { | ||
| PostHog.reset() | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Resolve duplicate heading issue: "### iOS" and "### Android" appear twice in the same context.
Lines 195 and 229 both introduce "### iOS" and "### Android" sections under the same parent ("## 5. Use deep links between platforms"). This creates ambiguous duplicate headings (MD024 warning).
Recommend renaming the sections to disambiguate:
- Line 195: Change
### iOSto### iOS Implementation(or similar variant) - Line 229: Change
### Androidto### Android Implementation
Alternatively, elevate one pair to ## if they represent top-level subsections within the deep links guidance.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 195-195: Multiple headings with the same content
(MD024, no-duplicate-heading)
[warning] 229-229: Multiple headings with the same content
(MD024, no-duplicate-heading)
🤖 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 @.claude/skills/integration-expo/references/identify-users.md around lines
195 - 258, The markdown file has duplicate heading levels creating a MD024
warning. The file contains two sets of "### iOS" and "### Android" headings
under the same parent section. To fix this, rename the first or second
occurrence of these duplicate headings to make them unique. For example, change
the first pair to "### iOS Implementation" and "### Android Implementation" or
rename the second pair similarly. Alternatively, elevate one complete pair of
iOS and Android sections to use "## " level headings if they represent distinct
top-level subsections within the deep links guidance section.
|
|
||
| ## Installation | ||
|
|
||
| Our React Native enables you to integrate PostHog with your React Native project. For React Native projects built with Expo, there are no mobile native dependencies outside of supported Expo packages. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix incomplete phrase: add "SDK" after "React Native".
Line reads: "Our React Native enables you to integrate..." — should be "Our React Native SDK enables you to integrate..."
🧰 Tools
🪛 LanguageTool
[style] ~5-~5: This phrase is redundant. Consider using “outside”.
Context: ...there are no mobile native dependencies outside of supported Expo packages. To install, a...
(OUTSIDE_OF)
🤖 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 @.claude/skills/integration-expo/references/react-native.md at line 5, The
opening sentence in the React Native integration reference is missing the word
"SDK" which makes it grammatically incomplete. Find the phrase "Our React Native
enables you to integrate" and add "SDK" between "React Native" and "enables" so
it reads "Our React Native SDK enables you to integrate PostHog with your React
Native project."
| - posthog-react-native is the React Native SDK package name (same as bare RN) | ||
| - Use expo-constants with app.config.js extras for POSTHOG_PROJECT_TOKEN and POSTHOG_HOST (NOT react-native-config) | ||
| - Access config via `Constants.expoConfig?.extra?.posthogProjectToken` in your posthog.ts config file | ||
| - For expo-router, wrap PostHogProvider in app/_layout.tsx and manually track screens with `posthog.screen(pathname, params)` in a useEffect | ||
| - posthog-react-native is the React Native SDK package name | ||
| - Use react-native-config to load POSTHOG_PROJECT_TOKEN and POSTHOG_HOST from .env (variables are embedded at build time, not runtime) | ||
| - react-native-svg is a required peer dependency of posthog-react-native (used by the surveys feature) and must be installed alongside it | ||
| - Place PostHogProvider INSIDE NavigationContainer for React Navigation v7 compatibility | ||
| - When a reverse proxy is configured, both /static/* AND /array/* must route to the assets origin (us-assets.i.posthog.com or eu-assets.i.posthog.com). |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Contradictory configuration guidance—clarify Expo vs. bare React Native patterns.
Lines 43–44 explicitly state "NOT react-native-config" and direct users to expo-constants, but lines 47–48 then direct users to use react-native-config. These instructions conflict directly.
Additionally, line 42 and line 46 are duplicates (both state "posthog-react-native is the React Native SDK package name").
For an Expo-focused skill, either:
- Remove the react-native-config guidance (lines 47–48) if it's only for bare RN and not Expo, or
- Clearly separate the Expo pattern (expo-constants) from the bare React Native pattern (react-native-config), labeling each explicitly.
The codebase context shows the implementation uses expo-constants, which aligns with lines 43–44 but contradicts lines 47–48.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 50-50: Spaces inside emphasis markers
(MD037, no-space-in-emphasis)
🪛 SkillSpector (2.2.3)
[error] 47: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
🤖 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 @.claude/skills/integration-expo/SKILL.md around lines 42 - 50, The SKILL.md
file contains contradictory configuration guidance for PostHog setup. Remove the
duplicate statement about posthog-react-native being the React Native SDK
package name that appears on both line 42 and line 46. Then address the
contradiction between the expo-constants approach (lines 43–44) and the
react-native-config approach (lines 47–48): since this is an Expo-focused skill
file, either remove the react-native-config guidance entirely (lines 47–48) if
it is only applicable to bare React Native projects, or clearly separate the two
patterns with explicit section headers (such as "For Expo:" and "For Bare React
Native:") so developers understand which approach applies to their project type.
The codebase currently implements the expo-constants pattern, so align the
documentation with that approach.
| useEffect(() => { | ||
| if (previousPathname.current !== pathname) { | ||
| posthog.screen(pathname, { | ||
| previous_screen: previousPathname.current ?? null, | ||
| ...params, | ||
| }); | ||
| previousPathname.current = pathname; | ||
| } | ||
| }, [pathname, params]); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major
🧩 Analysis chain
🌐 Web query:
In posthog-react-native v4.50.0, does autocapture.captureScreens: truealready emit screen-view events for Expo Router/React Navigation, and will manualposthog.screen(...) on route changes cause duplicate screen events?
💡 Result:
In posthog-react-native v4.50.0, the behavior of autocapture.captureScreens depends on the navigation library you are using. For Expo Router, official PostHog documentation explicitly advises against using autocapture.captureScreens due to incompatibility issues [1]. Enabling it can cause errors such as "Couldn't get the navigation state" because Expo Router's structure often places the PostHog provider outside of the required navigation context [1]. Instead, you should manually capture screen views by calling posthog.screen(...) inside the components or hooks of your routes [2][3]. Regarding duplicate events, if you are using a navigation setup where automatic tracking is supported (such as React Navigation v6 or lower, or react-native-navigation), enabling autocapture while also manually calling posthog.screen(...) will typically result in duplicate events [2][3]. If you have manually implemented screen tracking, you should set captureScreens: false in your autocapture configuration to prevent this duplication [2][4]. For users on React Navigation v7 or higher, automatic screen tracking is not supported due to architectural changes in the navigation library [5][6]. In these cases, you must manually call posthog.screen(...) [2][6], and no duplication will occur from the autocapture setting because the automatic mechanism does not function [5][6].
Citations:
- 1: posthog-react-native with expo-router and autocapture trigger useNavigationState error [Error: Couldn't get the navigation state. Is your component inside a navigator?] PostHog/posthog-js#2740
- 2: https://posthog.com/docs/libraries/react-native
- 3: https://preview.posthog.com/docs/libraries/react-native.md
- 4: https://posthog.com/docs/product-analytics/autocapture
- 5: Support screen autocapture with @react-navigation/native v7+ PostHog/posthog-js#2349
- 6: https://github.com/PostHog/posthog.com/blob/master/contents/docs/libraries/react-native/index.mdx
🏁 Script executed:
# Check package.json for PostHog version and navigation libraries
cat package.json | grep -A 5 -B 5 "posthog\|expo\|navigation"Repository: aymengmar/react-native-lingua
Length of output: 1864
🏁 Script executed:
# Read the _layout.tsx file to see the full context
head -n 120 app/_layout.tsxRepository: aymengmar/react-native-lingua
Length of output: 3573
Disable autocapture.captureScreens for Expo Router compatibility.
With Expo Router, enabling autocapture.captureScreens is incompatible and can cause errors because the PostHog provider is placed outside the navigation context required for automatic tracking. For React Navigation v7+ (which this app uses), automatic screen tracking isn't supported anyway. Your manual posthog.screen() calls (lines 35-43) are the correct approach—set captureScreens: false to prevent conflicts.
🔧 Suggested patch
<PostHogProvider
client={posthog}
autocapture={{
- captureScreens: true,
+ captureScreens: false,
captureTouches: true,
propsToCapture: ["testID"],
}}
>🤖 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 `@app/_layout.tsx` around lines 35 - 43, The PostHog configuration has
automatic screen capture enabled, which conflicts with Expo Router since the
PostHog provider is placed outside the navigation context. Locate the PostHog
provider initialization code and set the autocapture.captureScreens option to
false. This allows the manual posthog.screen() calls in the useEffect hook
(lines 35-43) to handle screen tracking without interference from automatic
capture that isn't compatible with React Navigation v7+.
| posthog.screen(pathname, { | ||
| previous_screen: previousPathname.current ?? null, | ||
| ...params, | ||
| }); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether analytics calls spread raw route/search params into payloads.
fd -e ts -e tsx . app components lib | xargs rg -nP 'posthog\.(screen|capture)\([^)]*\{[^}]*\.\.\.(params|searchParams|query)[^}]*\}'Repository: aymengmar/react-native-lingua
Length of output: 171
🏁 Script executed:
cat -n app/_layout.tsx | head -60Repository: aymengmar/react-native-lingua
Length of output: 2539
🏁 Script executed:
# Search for PostHog calls with simpler pattern
rg -n 'posthog\.' app/_layout.tsx -A 3 -B 1Repository: aymengmar/react-native-lingua
Length of output: 352
🏁 Script executed:
# Check if params filtering/sanitization exists anywhere
rg -n 'sanitized|filter.*params|sensitive' app/_layout.tsxRepository: aymengmar/react-native-lingua
Length of output: 167
🏁 Script executed:
# Search for all posthog calls in the codebase to see if this pattern is used elsewhere
rg -n 'posthog\.(screen|capture|identify)' app components lib -A 2Repository: aymengmar/react-native-lingua
Length of output: 4044
🏁 Script executed:
# Check what types of params are used in route definitions or deep links
rg -n 'useGlobalSearchParams|query.*token|query.*code|query.*email' app components -B 2 -A 2Repository: aymengmar/react-native-lingua
Length of output: 635
Filter sensitive data from URL params before sending to analytics.
Line 39 spreads params from useGlobalSearchParams() directly into posthog.screen() without filtering. URL query parameters—especially from deep links in auth flows—can contain sensitive data like email verification codes, password reset tokens, or other credentials, which will be leaked to analytics.
🔧 Suggested patch (redaction filter before capture)
useEffect(() => {
if (previousPathname.current !== pathname) {
+ const sanitizedParams = Object.fromEntries(
+ Object.entries(params).filter(
+ ([key]) => !/(token|code|secret|password|email)/i.test(key)
+ )
+ );
+
posthog.screen(pathname, {
previous_screen: previousPathname.current ?? null,
- ...params,
+ ...sanitizedParams,
});
previousPathname.current = pathname;
}
}, [pathname, params]);This aligns with the guideline: "Never expose secret keys or sensitive credentials in the mobile app."
📝 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.
| posthog.screen(pathname, { | |
| previous_screen: previousPathname.current ?? null, | |
| ...params, | |
| }); | |
| useEffect(() => { | |
| if (previousPathname.current !== pathname) { | |
| const sanitizedParams = Object.fromEntries( | |
| Object.entries(params).filter( | |
| ([key]) => !/(token|code|secret|password|email)/i.test(key) | |
| ) | |
| ); | |
| posthog.screen(pathname, { | |
| previous_screen: previousPathname.current ?? null, | |
| ...sanitizedParams, | |
| }); | |
| previousPathname.current = pathname; | |
| } | |
| }, [pathname, params]); |
🤖 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 `@app/_layout.tsx` around lines 37 - 40, The posthog.screen() call in the
analytics tracking block spreads all params from useGlobalSearchParams()
directly without filtering, which can expose sensitive data like verification
codes or reset tokens to analytics. Create a helper function to filter out known
sensitive parameters (such as those containing tokens, codes, credentials, or
verification-related keys) and apply this filter to params before spreading them
into the posthog.screen() call. Only send non-sensitive, safe parameters to the
analytics service.
Source: Coding guidelines
| const handleSignOut = async () => { | ||
| posthog.capture('user_signed_out'); | ||
| posthog.reset(); | ||
| await signOut(); | ||
| router.replace('/(auth)/sign-in'); | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the file and the specific function
cat -n app/\(tabs\)/profile.tsx | head -30Repository: aymengmar/react-native-lingua
Length of output: 1418
🏁 Script executed:
# Search for the signOut function to understand its behavior
rg -B 3 -A 10 'signOut\s*=' --type ts --type tsx | head -50Repository: aymengmar/react-native-lingua
Length of output: 195
🏁 Script executed:
# Also check how posthog is imported and used in the file
rg -B 2 -A 5 'posthog' app/\(tabs\)/profile.tsxRepository: aymengmar/react-native-lingua
Length of output: 890
🏁 Script executed:
# Look for signOut implementation in the codebase
fd -e ts -e tsx -exec rg -l 'export.*signOut|function signOut' {} \;Repository: aymengmar/react-native-lingua
Length of output: 384
🏁 Script executed:
# Search for signOut usage patterns in the codebase
rg 'signOut' --type ts --type tsx -B 2 -A 2Repository: aymengmar/react-native-lingua
Length of output: 195
🏁 Script executed:
# Check if there's any error handling related to auth operations
rg 'try\s*{' --type ts --type tsx -A 15 | grep -A 15 'signOut\|auth'Repository: aymengmar/react-native-lingua
Length of output: 245
🌐 Web query:
Clerk expo signOut error handling behavior throws exception
💡 Result:
In the Clerk Expo SDK, the signOut function is an asynchronous operation that returns a Promise [1][2]. Its behavior regarding exception throwing and error handling can be summarized as follows: 1. Asynchronous Error Handling: Because signOut returns a Promise, it can reject if an error occurs during the sign-out process (e.g., network issues or API failures) [3]. You should always wrap calls to signOut in a try/catch block to handle these potential exceptions [3]. 2. Development and Environment Context: - Web vs. Native: The signOut function is designed for both web and native environments, but specific implementation details differ [4]. In some earlier or beta versions, developers reported issues where signOut attempted to access browser-specific objects (like window), which caused errors in native environments [5]. Current, stable versions of the SDK are designed to handle native environments correctly. - Native State Sync: The SDK maintains synchronization between the JavaScript layer and native modules (iOS/Android). Internal native-side sign-out logic is designed to catch and handle errors to ensure that the application's state, such as SecureStore and keychain items, is cleared even if the primary sign-out request fails [6][7]. 3. Recommended Implementation: When building a custom sign-out flow, follow this pattern to ensure robust error handling [3]: async function handleSignOut { try { await signOut; // Perform successful navigation or UI updates } catch (err) { // Log the error or handle it based on the ClerkError interface console.error("Sign-out failed:", err); } } If you encounter specific "already signed in" or session-related errors after signing out, ensure your project is updated to the latest version of @clerk/expo, as recent patches have explicitly addressed issues where stale JWTs in SecureStore could prevent successful re-authentication [6][7].
Citations:
- 1: https://clerk.com/docs/expo/reference/hooks/use-auth
- 2: https://clerk.com/docs/expo/reference/objects/clerk
- 3: https://clerk.com/docs/guides/development/custom-flows/authentication/sign-out
- 4: https://clerk.com/docs/expo/reference/components/unstyled/sign-out-button
- 5: signOut() and isLoaded not working on IOS with @clerk/clerk-expo@beta clerk/javascript#2930
- 6: fix(expo): clear JWT from SecureStore on sign-out to prevent stale session errors clerk/javascript#8154
- 7: feat(expo): re-introduce two-way JS/native session sync clerk/javascript#8088
🌐 Web query:
posthog reset timing analytics session sign out
💡 Result:
To manage PostHog analytics sessions during a sign-out event, you should use the posthog.reset method [1][2][3]. This function is specifically designed to be called when a user logs out to ensure that future events on that device are not incorrectly associated with the previous user [1][4]. What posthog.reset does: - It clears the current Session ID and associated super properties [2][3]. - It clears user identification and generates a new, random distinct_id for the subsequent anonymous session [2][3]. - It clears cached data and consent settings [2][3]. - It starts a fresh session, which also triggers a new session replay [5]. Usage and Recommendations: - Call posthog.reset immediately upon logout to prevent data merging across different users who might share the same computer or browser [1][4]. - By default, posthog.reset creates a new distinct ID but keeps the existing device ID. If you need to treat the device as completely new (e.g., for privacy or security reasons), you can call posthog.reset(true), which will also reset the device_id [1][2][3]. - Use this method only when a user explicitly logs out. Calling it at other times may cause unintended split sessions [2][3]. Note on Implementation: Some users have reported potential issues with event capturing or network recording after calling reset in specific SDK versions [6][7]. If you experience dropped events or recording issues, ensure you are on the latest SDK version, as these are tracked as bugs in the library repository [6][7].
Citations:
- 1: https://preview.posthog.com/docs/getting-started/identify-users.md
- 2: https://posthog.com/docs/references/posthog-js.md
- 3: https://posthog.com/docs/references/posthog-js
- 4: https://preview.posthog.com/docs/libraries/js/features.md
- 5: https://posthog.com/docs/data/sessions
- 6: reset() stops recording and transmission of events PostHog/posthog-js#1502
- 7: Calling reset() can drop network captures PostHog/posthog-js#2668
🏁 Script executed:
# Check if clearSelectedLanguage is used elsewhere in sign-out flow
rg 'clearSelectedLanguage' --type ts --type tsx -B 2 -A 2Repository: aymengmar/react-native-lingua
Length of output: 195
🏁 Script executed:
# Look for other auth-related error handling in the codebase
rg 'useAuth\|signOut' --type ts --type tsx -A 5 | head -80Repository: aymengmar/react-native-lingua
Length of output: 195
🏁 Script executed:
# Try without the type specification for tsx files
rg 'clearSelectedLanguage' -B 2 -A 2Repository: aymengmar/react-native-lingua
Length of output: 167
🏁 Script executed:
# Check other parts of the codebase for signOut error handling patterns
rg 'signOut' -A 5 | head -100Repository: aymengmar/react-native-lingua
Length of output: 167
🏁 Script executed:
# List the files in the repository to understand structure better
find . -type f -name "*.tsx" -o -name "*.ts" | head -20Repository: aymengmar/react-native-lingua
Length of output: 894
🏁 Script executed:
# Search for clearSelectedLanguage in any file
grep -r "clearSelectedLanguage" . 2>/dev/nullRepository: aymengmar/react-native-lingua
Length of output: 470
🏁 Script executed:
# Search for other signOut usages
grep -r "signOut" . --include="*.tsx" --include="*.ts" 2>/dev/nullRepository: aymengmar/react-native-lingua
Length of output: 540
🏁 Script executed:
# Check how the profile screen uses clearSelectedLanguage and if it relates to auth
cat -n ./app/\(tabs\)/profile.tsx | head -80Repository: aymengmar/react-native-lingua
Length of output: 2963
🏁 Script executed:
# Check the languageStore to understand if it's auth-related
cat -n ./store/languageStore.tsRepository: aymengmar/react-native-lingua
Length of output: 1371
Add error handling to sign-out flow and ensure analytics reset is called correctly.
The signOut() call can fail with network or API errors (per Clerk documentation), but the current code has no error handling. If signOut() fails, the user remains signed in but posthog.reset() has already executed, creating an inconsistent state. Wrap the flow in try/catch and ensure reset happens after successful sign-out. Also consider calling clearSelectedLanguage() for complete user state cleanup.
Suggested fix
const handleSignOut = async () => {
- posthog.capture('user_signed_out');
- posthog.reset();
- await signOut();
- router.replace('/(auth)/sign-in');
+ try {
+ posthog.capture('user_signed_out');
+ await signOut();
+ posthog.reset();
+ clearSelectedLanguage();
+ router.replace('/(auth)/sign-in');
+ } catch (error) {
+ console.error('Sign-out failed:', error);
+ posthog.captureException(error as Error);
+ }
};🤖 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 `@app/`(tabs)/profile.tsx around lines 15 - 20, The handleSignOut function
needs error handling for the signOut() call which can fail per Clerk
documentation. Wrap the entire async flow in a try/catch block. Move the
posthog.reset() call to execute only after a successful signOut() to prevent
inconsistent state where the user remains signed in but analytics are cleared.
Additionally, add a call to clearSelectedLanguage() within the try block for
complete user state cleanup. In the catch block, handle any errors appropriately
(consider logging or showing an error message to the user) and ensure navigation
does not occur if sign-out fails.
| if (!onResend || loading) return; | ||
| setLoading(true); | ||
| setError(""); | ||
| posthog.capture("email_verification_code_resent", { email }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Track resend success only after the resend call succeeds.
email_verification_code_resent is emitted before await onResend(), so failed attempts are counted as successful resends. Emit this event after a successful await, and optionally add a separate failure event in catch.
Proposed fix
const handleResend = async () => {
if (!onResend || loading) return;
setLoading(true);
setError("");
- posthog.capture("email_verification_code_resent", { email });
try {
await onResend();
+ posthog.capture("email_verification_code_resent", { email });
} catch (err: any) {
const message =
err?.errors?.[0]?.message || "Failed to resend code.";
+ posthog.capture("email_verification_resend_failed", {
+ email,
+ error_message: message,
+ });
setError(message);
} finally {
setLoading(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 `@components/VerificationModal.tsx` at line 82, The posthog.capture call for
"email_verification_code_resent" is currently being emitted before awaiting the
onResend() call, which means failed resend attempts are incorrectly tracked as
successful. Move the posthog.capture("email_verification_code_resent", { email
}) event to execute only after the await onResend() completes successfully.
Additionally, consider adding a separate failure event (such as
"email_verification_code_resent_failed") in the catch block to track
unsuccessful resend attempts.
| - [ ] Run a full production build (the wizard only verified the files it touched) and fix any lint or type errors introduced by the generated code. | ||
| - [ ] Run the test suite — call sites that were rewritten or instrumented may need updated mocks or fixtures. | ||
| - [ ] Add `POSTHOG_PROJECT_TOKEN` and `POSTHOG_HOST` to `.env.example` and any bootstrap scripts so collaborators know what to set. | ||
| - [ ] Confirm the returning-visitor path also calls `identify` — currently `identify` is only called on fresh sign-in/sign-up completion; returning sessions restored from Clerk's token cache will be on anonymous distinct IDs until the user signs in again. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify where identify is called and whether app-boot/auth-restored paths include it.
fd -e ts -e tsx . app lib | xargs rg -nP '\bidentify\s*\('
fd -e ts -e tsx _layout app | xargs rg -nP 'useAuth|useUser|isSignedIn|tokenCache|onRehydrate|_hasHydrated'Repository: aymengmar/react-native-lingua
Length of output: 1285
🏁 Script executed:
cat -n app/_layout.tsxRepository: aymengmar/react-native-lingua
Length of output: 4350
🏁 Script executed:
# Search for all identify calls in the entire codebase, not just app/lib
rg -n 'identify\s*\(' --type ts --type tsxRepository: aymengmar/react-native-lingua
Length of output: 195
🏁 Script executed:
# Look for useEffect hooks that might handle auth state changes
ast-grep outline app/_layout.tsxRepository: aymengmar/react-native-lingua
Length of output: 325
🏁 Script executed:
# Search for all identify calls across the entire repo
rg -n 'identify' --type ts --type tsxRepository: aymengmar/react-native-lingua
Length of output: 195
🏁 Script executed:
# Check if there's a custom hook or utility handling Clerk auth changes
fd -name '*.ts' -o -name '*.tsx' | xargs rg -l 'useAuth|Clerk' | head -20Repository: aymengmar/react-native-lingua
Length of output: 637
🏁 Script executed:
# Search for identify calls more broadly
rg 'identify' app/Repository: aymengmar/react-native-lingua
Length of output: 277
🏁 Script executed:
# Check the sign-in and sign-up files to see context
cat -n app/'(auth)'/sign-in.tsx | grep -A 5 -B 5 identifyRepository: aymengmar/react-native-lingua
Length of output: 621
🏁 Script executed:
# Check if there's any auth listener or effect in other files
rg 'useAuth\|useUser' app/ --type ts --type tsx -lRepository: aymengmar/react-native-lingua
Length of output: 195
🏁 Script executed:
# Check all files that use useAuth
rg 'useAuth' app/ -lRepository: aymengmar/react-native-lingua
Length of output: 229
🏁 Script executed:
# Look for any PostHog identify in the entire codebase
find app -name "*.tsx" -o -name "*.ts" | xargs rg 'identify'Repository: aymengmar/react-native-lingua
Length of output: 277
🏁 Script executed:
# Check if there's a Clerk auth listener or setup elsewhere
find . -name "*.tsx" -o -name "*.ts" | xargs rg 'onSignIn|onSignUp|onClerkLoaded'Repository: aymengmar/react-native-lingua
Length of output: 171
🏁 Script executed:
# Check profile.tsx to see how it uses useAuth
cat -n app/'(tabs)'/profile.tsx | head -80Repository: aymengmar/react-native-lingua
Length of output: 2963
🏁 Script executed:
# Check auth/_layout.tsx to see its full content
cat -n app/'(auth)'/_layout.tsxRepository: aymengmar/react-native-lingua
Length of output: 565
Add identify() call on app boot for already-authenticated sessions to close the returning-session identity gap.
The codebase currently calls identify() only during fresh sign-in/sign-up flows (sign-in.tsx line 49, sign-up.tsx line 63). Users restored from Clerk's tokenCache on app boot will have isSignedIn=true but will NOT trigger identify(), leaving them tracked under anonymous distinct IDs until their next manual sign-in. This breaks funnel attribution and retention cohorts.
Add an identify() call in the _layout.tsx useEffect (lines 45-63) when isSignedIn transitions from false to true or on initial app boot with an existing session, ensuring all authenticated users—whether fresh or restored from token cache—are properly identified in PostHog.
🤖 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 `@posthog-setup-report.md` at line 38, The identify() function is currently
only called in sign-in.tsx and sign-up.tsx during fresh authentication flows,
but users restored from Clerk's tokenCache on app boot are not being identified,
leaving them under anonymous distinct IDs. In the _layout.tsx file, locate the
useEffect hook (around lines 45-63) and add an identify() call that triggers
when isSignedIn becomes true, particularly to handle the case where a user
already has an authenticated session restored from the token cache. This ensures
all authenticated users—both fresh and returning from token cache—are properly
identified in PostHog rather than only those completing a manual sign-in flow.
Summary by CodeRabbit
Release Notes
New Features
Chores