Skip to content

Conversation

@OrangeAndGreen
Copy link
Contributor

https://dimagi.atlassian.net/browse/CCCT-1868

Product Description

Fixes a repeated crash at app startup after an initial crash due to a corrupt Connect DB.
Note this PR does not fix the original crash (i.e. why the DB got corrupted).
It just makes sure the app only crashes once when that happens.

Technical Summary

Deleting Connect DB immediately when crash DB is called, so it's gone when the app restarts (to avoid crashing again repeatedly at app startup).

Also added an empty check to the passphrase when opening handle to Connect DB. If that were the case it would explain a corrupted DB error, so this could help determine how the app gets into this state to begin with.

Feature Flag

PersonalID

Safety Assurance

Safety story

I tested by starting with a clean PersonalID configuration, then temporarily overriding the passphrase to null in the code in order to cause the app to detect the DB as corrupt. Before applying the fix, I could observe the app crashing repeatedly on startup. After the fix, the app only crashes once, and when restarted the user sees PersonalID de-configured and a message explaining that something went wrong and they will need to re-configure PersonalID.

Automated test coverage

None

QA Plan

This will be difficult/impossible for QA to test since we're not sure how to cause the corrupt DB scenario that opens up this second bug.

… when the app restarts (to avoid crashing again repeatedly at app startup).

Also added an empty check to the passphrase when opening handle to Connect DB.
@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

📝 Walkthrough

Walkthrough

The changes relocate user forget logic from GlobalErrorUtil to ConnectDatabaseHelper. Previously, Global error handling would conditionally invoke PersonalIdManager.forgetUser() on certain errors. Now, the forgetUser call is triggered in ConnectDatabaseHelper when a crash occurs on the Connect database. Additionally, the passphrase validation in ConnectDatabaseHelper is tightened to treat empty passphrases (length 0) the same as null, throwing an IllegalStateException if Connect DB access is attempted without a valid passphrase.

Sequence Diagram

sequenceDiagram
    participant App as App/Caller
    participant CDB as ConnectDatabaseHelper
    participant PIM as PersonalIdManager
    participant GEU as GlobalErrorUtil
    
    rect rgb(240, 248, 255)
    Note over CDB,PIM: New Flow (Post-Change)
    App->>CDB: DB operation / crash
    alt Passphrase is null or empty
        CDB->>CDB: Throw IllegalStateException
    else Crash with error
        CDB->>PIM: forgetUser(PERSONAL_ID_FORGOT_USER_DB_ERROR)
        PIM->>PIM: Clear user data
        CDB->>CDB: Throw RuntimeException
    end
    end
    
    rect rgb(255, 240, 240)
    Note over GEU: Old Flow (Pre-Change)
    App->>GEU: Global error handling
    GEU->>GEU: Conditionally check deleteConnectStorage flag
    opt deleteConnectStorage true
        GEU->>PIM: forgetUser(...)
    end
    GEU->>GEU: Handle error (no-op for forgetUser now)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • ConnectDatabaseHelper.java: Requires verification that the new forgetUser call on crash is appropriate and that passphrase validation logic is correct
  • GlobalErrorUtil.java: Verify that removing forgetUser logic doesn't cause unintended regressions and that error handling still works as expected
  • Integration point: Confirm that moving user forget responsibility from GlobalErrorUtil to ConnectDatabaseHelper doesn't create duplicate calls or missed scenarios

Possibly related PRs

Suggested labels

skip-integration-tests, QA Note

Suggested reviewers

  • shubham1g5
  • conroy-ricketts
  • Jignesh-dimagi

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly summarizes the main fix: preventing repeated crashes at app startup when the Connect DB is corrupt.
Description check ✅ Passed The description covers all major sections of the template including product description, technical summary, feature flag, and safety assurance with manual testing details.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch db_crash_point

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4d4e97 and 2cdf213.

📒 Files selected for processing (2)
  • app/src/org/commcare/connect/database/ConnectDatabaseHelper.java (3 hunks)
  • app/src/org/commcare/utils/GlobalErrorUtil.java (0 hunks)
💤 Files with no reviewable changes (1)
  • app/src/org/commcare/utils/GlobalErrorUtil.java
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: shubham1g5
Repo: dimagi/commcare-android PR: 0
File: :0-0
Timestamp: 2025-05-08T11:08:18.530Z
Learning: PR #3048 "Phase 4 Connect PR" introduces a substantial feature called "Connect" to the CommCare Android app, which includes messaging, job management, delivery tracking, payment processing, authentication flows, and learning modules. It follows a modern architecture using Navigation Components with three navigation graphs, segregated business logic in Manager classes, and proper database persistence.
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3108
File: app/src/org/commcare/fragments/connect/ConnectDeliveryDetailsFragment.java:163-180
Timestamp: 2025-06-06T19:52:53.173Z
Learning: In the CommCare Android Connect feature, database operations like ConnectJobUtils.upsertJob should be allowed to crash rather than being wrapped in try-catch blocks. The team prefers fail-fast behavior for database errors instead of graceful error handling.
Learnt from: shubham1g5
Repo: dimagi/commcare-android PR: 2949
File: app/src/org/commcare/fragments/connectId/ConnectIdPasswordVerificationFragment.java:173-247
Timestamp: 2025-03-10T08:16:29.416Z
Learning: In the ConnectIdPasswordVerificationFragment, password comparisons should use MessageDigest.isEqual() rather than equals() to prevent timing attacks, and empty password validation should be implemented before verification attempts.
Learnt from: shubham1g5
Repo: dimagi/commcare-android PR: 2949
File: app/src/org/commcare/fragments/connectId/ConnectIdPasswordVerificationFragment.java:173-247
Timestamp: 2025-03-10T08:16:29.416Z
Learning: In the Connect ID feature, passwords are encrypted at the database layer, so there's no need to hash passwords before sending them over the network as they're already secured at that level.
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3108
File: app/src/org/commcare/fragments/connect/ConnectDownloadingFragment.java:74-78
Timestamp: 2025-06-06T19:54:26.428Z
Learning: In ConnectDownloadingFragment.java and similar Connect-related code, the team prefers to let "should never happen" scenarios like null app records crash rather than add defensive null checks, following a fail-fast philosophy to catch programming errors during development.
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3248
File: app/src/org/commcare/fragments/connect/ConnectUnlockFragment.java:0-0
Timestamp: 2025-07-29T14:10:55.131Z
Learning: In ConnectUnlockFragment.java, opportunityId values are expected to always contain valid integer strings. Integer.parseInt() should be allowed to throw NumberFormatException if invalid data is encountered, following the fail-fast philosophy used throughout the Connect feature for data contract violations.
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3108
File: app/src/org/commcare/activities/StandardHomeActivityUIController.java:0-0
Timestamp: 2025-06-20T13:21:20.908Z
Learning: User OrangeAndGreen prefers to handle code issues in separate PRs rather than immediately fixing them in the current PR when they are not directly related to the main changes.
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3248
File: app/src/org/commcare/connect/network/ApiPersonalId.java:0-0
Timestamp: 2025-07-29T14:56:40.681Z
Learning: User OrangeAndGreen prefers to defer ApiPersonalId refactoring (specifically InputStream resource management improvements) to separate PRs rather than mixing them with Connect feature work, even when the code is already in master.
📚 Learning: 2025-06-06T19:52:53.173Z
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3108
File: app/src/org/commcare/fragments/connect/ConnectDeliveryDetailsFragment.java:163-180
Timestamp: 2025-06-06T19:52:53.173Z
Learning: In the CommCare Android Connect feature, database operations like ConnectJobUtils.upsertJob should be allowed to crash rather than being wrapped in try-catch blocks. The team prefers fail-fast behavior for database errors instead of graceful error handling.

Applied to files:

  • app/src/org/commcare/connect/database/ConnectDatabaseHelper.java
📚 Learning: 2025-03-10T08:16:29.416Z
Learnt from: shubham1g5
Repo: dimagi/commcare-android PR: 2949
File: app/src/org/commcare/fragments/connectId/ConnectIdPasswordVerificationFragment.java:173-247
Timestamp: 2025-03-10T08:16:29.416Z
Learning: In the ConnectIdPasswordVerificationFragment, password comparisons should use MessageDigest.isEqual() rather than equals() to prevent timing attacks, and empty password validation should be implemented before verification attempts.

Applied to files:

  • app/src/org/commcare/connect/database/ConnectDatabaseHelper.java
📚 Learning: 2025-05-08T11:08:18.530Z
Learnt from: shubham1g5
Repo: dimagi/commcare-android PR: 0
File: :0-0
Timestamp: 2025-05-08T11:08:18.530Z
Learning: PR #3048 "Phase 4 Connect PR" introduces a substantial feature called "Connect" to the CommCare Android app, which includes messaging, job management, delivery tracking, payment processing, authentication flows, and learning modules. It follows a modern architecture using Navigation Components with three navigation graphs, segregated business logic in Manager classes, and proper database persistence.

Applied to files:

  • app/src/org/commcare/connect/database/ConnectDatabaseHelper.java
📚 Learning: 2025-01-21T17:28:09.007Z
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 2912
File: app/src/org/commcare/android/database/connect/models/ConnectUserRecord.java:66-71
Timestamp: 2025-01-21T17:28:09.007Z
Learning: The ConnectUserRecord class in CommCare Android uses Persisting annotations with sequential indices for field persistence, and MetaField annotations for fields that have corresponding META constants. Fields should be documented with Javadoc comments explaining their purpose, format requirements, and relationships with other fields.

Applied to files:

  • app/src/org/commcare/connect/database/ConnectDatabaseHelper.java
📚 Learning: 2025-06-06T19:54:26.428Z
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3108
File: app/src/org/commcare/fragments/connect/ConnectDownloadingFragment.java:74-78
Timestamp: 2025-06-06T19:54:26.428Z
Learning: In ConnectDownloadingFragment.java and similar Connect-related code, the team prefers to let "should never happen" scenarios like null app records crash rather than add defensive null checks, following a fail-fast philosophy to catch programming errors during development.

Applied to files:

  • app/src/org/commcare/connect/database/ConnectDatabaseHelper.java
📚 Learning: 2025-06-13T15:53:12.951Z
Learnt from: OrangeAndGreen
Repo: dimagi/commcare-android PR: 3108
File: app/src/org/commcare/connect/network/PersonalIdApiHandler.java:51-56
Timestamp: 2025-06-13T15:53:12.951Z
Learning: For `PersonalIdApiHandler`, the team’s convention is to propagate `JSONException` as an unchecked `RuntimeException` so the app crashes, signalling a contract/implementation bug rather than attempting a graceful retry.

Applied to files:

  • app/src/org/commcare/connect/database/ConnectDatabaseHelper.java
🧬 Code graph analysis (1)
app/src/org/commcare/connect/database/ConnectDatabaseHelper.java (2)
app/src/org/commcare/connect/PersonalIdManager.java (1)
  • PersonalIdManager (62-574)
app/src/org/commcare/google/services/analytics/AnalyticsParamValue.java (1)
  • AnalyticsParamValue (9-207)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Lint Code Base
🔇 Additional comments (3)
app/src/org/commcare/connect/database/ConnectDatabaseHelper.java (3)

8-8: LGTM! Necessary imports for the new functionality.

The imports support the forgetUser call introduced in the crashDb method.

Also applies to: 10-10


55-57: Good defensive validation for empty passphrase.

Treating an empty passphrase (length 0) as invalid alongside null is appropriate, as an empty passphrase cannot decrypt the database. The short-circuit evaluation safely prevents NPE.


88-89: All verification concerns are addressed—the implementation is safe.

The code correctly handles the corrupt DB scenario:

  • DB access safety: ✓ ConnectUserDatabaseUtil.forgetUser() does not call getConnectStorage() and avoids re-accessing the corrupt database. It uses getGlobalStorage() for key records and directly deletes the DB via DatabaseConnectOpenHelper.deleteDb().
  • File deletion: ✓ DatabaseConnectOpenHelper.deleteDb() is explicitly called, confirming the DB file is actually deleted (not just cleared in-memory).
  • Idempotency: ✓ Repeated calls to deleteDb(), flag resets, and teardown() are all safe. Analytics reporting is guarded by dbExists() check.

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.

@@ -15,7 +13,6 @@ public static void addError(GlobalErrorRecord error) {

public static String handleGlobalErrors() {
Copy link
Contributor

Choose a reason for hiding this comment

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

@OrangeAndGreen Changes look good, only minor nit: Rename this to getGlobalErrorsIfAny as it's not handling now anything?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, makes sense. I went with just getGlobalErrors: be2aeb7

@OrangeAndGreen
Copy link
Contributor Author

@damagatchi Retest this please

@OrangeAndGreen OrangeAndGreen added the skip-integration-tests Skip android tests. label Nov 12, 2025
@OrangeAndGreen
Copy link
Contributor Author

@damagatchi Retest this please

@OrangeAndGreen OrangeAndGreen merged commit d7489e3 into master Nov 12, 2025
7 checks passed
@OrangeAndGreen OrangeAndGreen deleted the db_crash_point branch November 12, 2025 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-integration-tests Skip android tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants