home-assistant / home-assistant/iOS
Reminders Sync duplicates items endlessly when the todo backend normalizes titles (alexa_devices)
- Dominant language
- Swift
- Stars
- 2.4k
- Forks
- 520
- Avg merge
- 7h 27m
- Merged PRs (30d)
- 264
Description
**iOS device model, version and app version**
Model Name: iPhone 11 Pro (`iPhone12,3`)
Software Version: iOS 26.5
App version: 2026.9.0
**Home Assistant Core Version**
2026.9.0 (Home Assistant OS 18.2)
**Describe the bug**
Reminders Sync creates an unbounded number of duplicates when the paired `todo`
entity is backed by an integration that normalizes item titles on write. It adds
one extra copy of every Reminders-origin item on every sync run, indefinitely.
I hit this with the `alexa_devices` integration. Amazon rewrites every list item
to sentence case when it stores it, so the title the app wrote back is never the
title it reads back.
The cause looks like the post-create linking step. `todo.add_item` does not
return the new item's `uid`, so `linkCreatedTodoItems` re-fetches the list and
identifies its own new item by exact title equality:
`Sources/App/Utilities/RemindersSync/RemindersSyncManager.swift`
```swift
for reminderId in reminderIds {
guard let snapshot = reminderSnapshots[reminderId],
let index = unlinkedItems
.firstIndex(where: { RemindersSyncItemSnapshot(todoItem: $0).title == snapshot.title })
else { continue }
```
When the backend has changed the title, `firstIndex` returns nil, the `continue`
fires, and **no link is saved**. On the next run the reminder is still unlinked,
so the planner's adoption step misses too, since it uses the same case-sensitive
comparison:
`Sources/App/Utilities/RemindersSync/RemindersSyncPlanner.swift`
```swift
.firstIndex(where: { reminders[$0]?.title == todoItem.title })
```
so the item is created again. Repeat forever.
The Home Assistant to Reminders direction is unaffected, because `createReminder`
stores the link immediately from `reminder.calendarItemIdentifier` and never
matches on title. That matches what I observed: only Reminders-origin items
multiply.
**To Reproduce**
1. Set up the `alexa_devices` integration so an Alexa list appears as a `todo`
entity.
2. Create a list on both sides and pair them in Reminders Sync, direction
Two-way.
3. Put an item in Apple Reminders whose title is not already sentence case, for
example `SYNC Apple Only` or `Almond Milk`.
4. Sync repeatedly.
Each sync adds one more copy on the Home Assistant side. In my trial a list that
should have held 3 items reached 27.
| Item | Copies after several syncs |
|---|---|
| `Sync shared` (originated in Reminders) | 9 |
| `Sync apple only` (originated in Reminders) | 8 |
| `Sync alexa only` (originated in Alexa) | 1 |
| `Sync alexa history` (completed, in Alexa) | 1 |
**Expected behavior**
The link is established even when the backend alters the title, so an item is
created once and not recreated on later syncs.
**Additional context**
Amazon's normalization rule, measured by writing eight strings through
`todo.add_item` and reading them back. It uppercases the first character and
lowercases the rest.
| Sent | Stored |
|---|---|
| `Milk` | `Milk` |
| `2% milk` | `2% milk` |
| `almond milk` | `Almond milk` |
| `Almond Milk` | `Almond milk` |
| `ALMOND MILK` | `Almond milk` |
| `Ben & Jerry's` | `Ben & jerry's` |
| `iPhone charger` | `Iphone charger` |
| `eggs and BACON` | `Eggs and bacon` |
A case-insensitive comparison at both sites would fix the common case. A more
robust fix would be to diff the list before and after the create rather than
matching on title at all, since any backend is free to alter what it stores.
Workarounds I found, in case they help others: use a one-way Home Assistant to
Reminders pairing, which cannot hit this path, or keep every Reminders title in
sentence case so the round trip leaves it unchanged.
Contributor guide
Research direction
Start in Sources/App/Utilities/RemindersSync/RemindersSyncManager.swift at linkCreatedTodoItems and compare its matching with Sources/App/Utilities/RemindersSync/RemindersSyncPlanner.swift. Reproduce with an alexa_devices todo entity and a Reminders title that is normalized on write. Done means Reminders-origin items link successfully and repeated syncs no longer create duplicates.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, swift
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100