chore(sync): upstream/main @ 85198f42b — 2026-06-04#49
Merged
Conversation
…Hardware#7631) When getIdToken() returns null and the token is already expired, clear the SharedPreferences cache instead of falling through to the stale token. This prevents infinite WebSocket retry loops after Firebase signing key rotations. Regression from 9d33d2e which preserved tokens on null refresh to avoid clearing near-expiry-but-valid tokens on transient failures. The fix preserves that intent (only clears when isExpirationDateValid is false) while closing the gap for truly expired tokens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ardware#7631) Update getAuthHeaderFixed simulation and test to verify that expired tokens are cleared when refresh returns null, matching the production fix. The test now expects an exception (triggering sign-out) instead of silently reusing the stale cached token. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tighten the clearing condition from !isExpirationDateValid (which includes the 5-min proactive refresh buffer) to expiry.isBefore(now) (truly expired only). Near-expiry but still-valid tokens are preserved on transient refresh failures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add isExpired parameter to test simulation to distinguish truly expired tokens from near-expiry tokens in the 5-min buffer. Add test verifying near-expiry tokens are preserved when refresh fails transiently. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…vert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sults When the user combined filters (e.g. a folder + Starred) and the server returned zero conversations, both the section title and the folder/Starred chip row hid themselves — the guard checked `conversations.length > 0` without considering whether a filter was active, so the only way to clear the filter was to restart the app. Add an `_hasActiveFilter` helper and OR it into the title and folder-tabs guards so the chips remain reachable whenever a filter is on. Also drop the older inline `!showStarredOnly && selectedFolderId == null` check on the "no conversations yet" hero in favour of the same helper, which fixes the same class of bug for the date filter.
Iterate a user's phone_numbers subcollection and call Twilio's
outgoing_caller_ids({sid}).delete() for each entry that carries a
twilio_sid. Best-effort: per-entry exceptions and the phone-number list
fetch are caught and logged so the caller (delete_account background
wipe) can keep going to the Firestore wipe even if Twilio is down.
Returns the count of successfully deleted caller IDs for visibility.
The lazy import of database.phone_calls keeps the test module
stub-friendly.
Refs BasedHardware#7640
Call delete_user_caller_ids(uid) inside _background_wipe_user_data before delete_user_data(uid) so the twilio_sid metadata is still available when the Twilio cleanup runs. Without this, deleting an account left every verified caller ID registered on our Twilio account forever. When another user (or the same user signing back up with a fresh uid) later tried to verify the same number, Twilio rejected the request with error 21450 and the backend returned "This phone number is already registered." The only remediation was an engineer manually calling outgoing_caller_ids.delete on the orphaned sid. Closes BasedHardware#7640
Five cases:
- calls delete_caller_id once per phone_numbers doc with a twilio_sid
- skips entries with missing / None / empty twilio_sid
- keeps going when one twilio call raises (rest still attempted)
- returns 0 cleanly when the user has no phone_numbers
- swallows a Firestore error fetching phone_numbers so the caller
can still proceed with the Firestore wipe
The test installs a stub for database.phone_calls before importing
utils.twilio_service so the unit suite does not require firebase_admin
or the rest of the database init chain.
…sults (BasedHardware#7639) ## Summary - Conversations page hid the section title and the folder/Starred chip row whenever the filtered result was empty, so combining filters (e.g. a folder + Starred) with no matches left the user with no way to clear the filter short of restarting the app. - The guard for both rows checked `conversations.length > 0` only — once the filtered fetch came back empty, the chips vanished. - This adds a small `_hasActiveFilter` helper (covers `showStarredOnly`, `selectedFolderId`, `selectedDate`) and ORs it into the title and folder-tabs visibility guards so the chips remain reachable whenever a filter is on. - Also reuses the helper in the "no conversations yet" hero guard, which previously inlined only the starred + folder checks and would mistakenly show the brand-new-user hero when only a date filter yielded zero results. ## Test plan - [ ] With one or more conversations, tap a folder chip → folder filter active, conversation list filters, chips stay visible. - [ ] With folder filter on, tap Starred → if no starred conversation in that folder, empty-state copy shows AND both chips remain visible so the user can clear either filter. - [ ] Clear both chips → list returns to default. - [ ] Brand-new account with zero conversations and no filter active → still shows the "No conversations yet" hero (unchanged). - [ ] With a date filter set and zero matches → empty-filter state shows (not the brand-new-user hero), chips visible.
…vert (BasedHardware#7638) ## Crash ActionItemsProvider.updateActionItemDueDate FlutterError - RangeError (length): Invalid value: Valid value range is empty: 3 package:omi/providers/action_items_provider.dart https://console.firebase.google.com/u/0/project/based-hardware/crashlytics/app/ios:com.friend-app-with-wearable.ios12/issues/d90ecbfbbb09ebbe4a55c0a08a034316?time=7d&types=crash&sessionEventKey=cbdc14cb933e4f28b52514202667422a_2225757600315898109 Logs: ``` Fatal Exception: FlutterError 0 ??? 0x0 ActionItemsProvider.updateActionItemDueDate (action_items_provider.dart) ``` ## Fix `index` was captured before `await api.updateActionItem(...)`. If `_actionItems` is modified during that await (e.g. user deletes items or list is refreshed), the stale `index` (e.g. 3) used in the catch/else revert blocks crashes with RangeError when the list now has fewer elements. Both revert paths now re-find the item by ID with `indexWhere` after the await instead of using the pre-await index, matching the pattern already used in the success path. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…BasedHardware#7637) ## Crash `_TranscriptionSettingsPageState._buildCurrentConfig` **Error:** `FlutterError - type 'bool' is not a subtype of type 'String' in type cast` `package:omi/pages/settings/transcription_settings_page.dart:293` **Crashlytics:** https://console.firebase.google.com/u/0/project/based-hardware/crashlytics/app/ios:com.friend-app-with-wearable.ios12/issues/2a64d646629244fdf0a70944b67c5f0a?time=7d&types=crash&sessionEventKey=47d6a65f63404b37851550687c6f0d6c_2225897673128842280 **Logs:** ``` Fatal Exception: FlutterError 0 ??? 0x0 new LinkedHashMap.from (dart:collection) 1 ??? 0x0 _TranscriptionSettingsPageState._buildCurrentConfig + 425 (transcription_settings_page.dart:425) 2 ??? 0x0 _TranscriptionSettingsPageState._saveCurrentProviderConfig + 394 (transcription_settings_page.dart:394) 3 ??? 0x0 _TranscriptionSettingsPageState._buildProviderSection.<fn> + 1218 (transcription_settings_page.dart:1218) 4 ??? 0x0 _DropdownButtonState._handleTap.<fn> + 1473 (dropdown.dart:1473) ``` ## Fix `Map<String, String>.from(requestJson['headers'])` does a hard cast of every value — if the user's saved JSON config has a non-string value in `headers` or `params` (e.g. `"keepAlive": true`, `"timeout": 30`), Dart throws a type cast error inside `LinkedHashMap.from`. Replaced both `Map<String, String>.from(...)` calls with `.map((k, v) => MapEntry(k.toString(), v.toString()))`, which safely coerces any value type to a string. Also tightened the guard from `!= null` to `is Map` so it won't attempt the conversion if the field is a non-map type. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Two P2 comments on BasedHardware#7642: - P2: in-function `from database import phone_calls` violated the backend module rule that all imports live at the top level. Hoisted to the top of utils/twilio_service.py; the test's sys.modules stub still works because it is installed before twilio_service is first imported. - P2: the outer `try/except` around `delete_caller_id(sid)` cannot ever see a Twilio API error — those are absorbed by delete_caller_id's own internal handler and surface as a False return on the warning path. The outer guard is reachable only if `_get_client()` raises (missing TWILIO_* env vars, credential rotation, etc.). Clarified the docstring, the inline comment, and the test-case comment so future maintainers don't conflate the two paths. Renamed the simulated error string in the test to match the actual failure mode.
…Hardware#7642) Closes BasedHardware#7640 ## Problem When a user deletes their omi account, the background wipe removes their Firestore data but never tells Twilio about it. The verified outgoing caller IDs they had registered stay on our Twilio account forever. Twilio enforces uniqueness at the account level, so any later attempt to verify the same number — by a brand-new omi user, or by the same person signing back up — fails with error 21450 → \"This phone number is already registered.\" Remediation today is an engineer manually calling `client.outgoing_caller_ids({sid}).delete()` after confirming no live omi user owns the number. ## Fix - New `utils.twilio_service.delete_user_caller_ids(uid)` reads `users/{uid}/phone_numbers`, calls `delete_caller_id(twilio_sid)` for every entry that has one. Best-effort: per-entry Twilio errors and the phone-list fetch are caught + logged so a momentary Twilio issue cannot leave the user half-deleted. - `_background_wipe_user_data` (in `routers/users.py`) calls it before `delete_user_data(uid)` — must run while the `twilio_sid` metadata is still in Firestore. - 5 unit tests cover: per-entry call, skipping entries without `twilio_sid`, continuing past a raising delete call, no-phone-numbers no-op, and a Firestore list failure being swallowed. ## Test plan - [x] `pytest tests/unit/test_twilio_account_deletion.py -v` → 5 passed - [x] `pytest tests/unit/test_twilio_service.py -v` (existing) → still passes - [x] black-formatted at line-length 120, skip-string-normalization - [x] No PII in commits / tests / PR
Sync fork with BasedHardware upstream/main through 2026-06-04. Major upstream changes: - feat: regenerate daily summary in app + backend endpoint (BasedHardware#7627) - fix(app): clear stale cached token when Firebase refresh fails (BasedHardware#7631/BasedHardware#7634) - fix(app): keep conversation filter chips visible on empty filtered results (BasedHardware#7639) - fix action items stale index RangeError in updateActionItemDueDate revert (BasedHardware#7638) - fix transcription settings Map cast crash on non-String header values (BasedHardware#7637) - backend: clean up Twilio caller IDs on account deletion + unit tests (BasedHardware#7642) - backend: greptile review — top-level import + clarify outer except scope Conflict resolutions: - app/pubspec.yaml: version conflict (HEAD 1.0.534+843 vs upstream 1.0.538+900); took upstream version as base, then bumped build number to +901 Build number bump: app +900 → +901, desktop CFBundleVersion 2 → 3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Upstream sync: merges BasedHardware/Omi upstream commits through 2026-06-04.
Changes from upstream (19 commits)
feat: regenerate daily summary in app three-dots menu + backend endpoint (feat: regenerate daily summary in app three-dots menu + backend endpoint BasedHardware/omi#7627)fix(app): clear stale cached token when Firebase refresh fails (Flutter: getAuthHeader() returns stale cached token when Firebase refresh fails BasedHardware/omi#7631, fix(app): clear stale cached token when Firebase refresh fails BasedHardware/omi#7634)test(app): add near-expiry token preservation test and update token refresh tests (Flutter: getAuthHeader() returns stale cached token when Firebase refresh fails BasedHardware/omi#7631)fix(app): keep conversation filter chips visible on empty filtered results (fix(app): keep conversation filter chips visible on empty filtered results BasedHardware/omi#7639)fix: action items stale index RangeError in updateActionItemDueDate revert (fix action items stale index RangeError in updateActionItemDueDate revert BasedHardware/omi#7638)fix: transcription settings Map cast crash on non-String header values (fix transcription settings Map cast crash on non-String header values BasedHardware/omi#7637)backend: clean up Twilio caller IDs when an account is deleted + unit tests (backend: clean up Twilio caller IDs when an account is deleted BasedHardware/omi#7642)backend: greptile review — top-level import + clarify outer except scopeConflict resolutions
1.0.534+843vs upstream1.0.538+900); took upstream as base then bumped build number to+901Build number bumps
pubspec.yaml:+900→+901Info.plistCFBundleVersion:2→3Verification
grep -rn '^<<<<<<< HEAD'— clean)upstream-sync-and-backend-policy.mdxflutter gen-l10n— flutter not available in remote environment; no ARB conflicts in this syncxcrun swift build— macOS-only, must be verified locally after pulling mainNote: Run
cd desktop && OMI_SKIP_TUNNEL=1 ./run.sh --yololocally to deploy to Omi Dev. Runxcrun swift build -c debug --package-path Desktoplocally to verify the Swift build.Generated by Claude Code