ical delete <id> can delete a different event than the one named, because ical add prints an ID that identifies no event. I lost a real calendar entry to this on v0.12.2 before working out what had happened.
Reproduction
$ ical add "Test event" -s "tomorrow 9am" -e "tomorrow 9:30am" -c Personal
Created: Test event
Calendar: Personal
When: 09:00 - 09:30
ID: 9933123D-95D5
$ ical delete 9933123D-95D5 -f
Deleted: <an unrelated event created a year earlier, in a different calendar>
The test event is still there afterwards. Nothing in the output suggests the wrong event was targeted.
ical show 9933123D-95D5 resolves the same way, so the problem is visible read-only too — it prints the details of some arbitrary event.
Cause
Two things compound.
1. ShortID truncates to 13 characters, which is never distinguishing.
// internal/ui/output.go
func ShortID(id string) string {
if len(id) <= 13 { return id }
return id[:13]
}
On macOS an event identifier is <store UUID>:<event UUID>. The first component is shared by every event in the store, so the first 13 characters are identical across the whole calendar database. PrintCreatedEvent and PrintUpdatedEvent print that prefix as the event's ID — the same string every time. The doc comment reasons that "two UUID segments … is enough to disambiguate events from the same source", but under this format the two segments are precisely the part that is not per-event.
2. findEventByPrefix trusts a lookup that did not round-trip.
// cmd/ical/commands/show.go
event, err := client.Event(input)
if err == nil {
return event, nil
}
EKEventStore.eventWithIdentifier: returns some event for a partial identifier instead of nil, so client.Event("9933123D-95D5") succeeds and returns an arbitrary event. Because it succeeds, control never reaches the prefix-matching branch below — which would have reported the ambiguity correctly, since hundreds of events share that prefix. show, update and delete all resolve their argument through this function, so delete -f acts on whatever came back.
The same hazard applies to the row-number path a few lines above: a cached ID from a previous listing that has since been deleted resolves to an unrelated event rather than falling through to the search, so the "Event may have been deleted since listing" comment describes a fallback that cannot trigger.
Suggested fix
PR incoming. Two commits:
- Check that a resolved event carries the ID that was asked for; anything else falls through to the prefix search, which reports the ambiguity. A partial ID that is genuinely unique still resolves, so prefix matching keeps working.
- Print full event IDs and drop
ShortID. The full ID is what --id needs, and there is no correct truncation under this identifier format.
The PR adds regression tests for the resolver behind a two-method interface, so they run without a live EventKit store. Three of them return an unrelated event against the current resolver.
Note on severity
This is silent and unrecoverable — macOS Calendar has no per-event trash, and the output gives no signal that the wrong event was hit. Anyone scripting ical add and feeding the printed ID back to ical delete will eventually delete something else. Worth a patch release once fixed.
ical delete <id>can delete a different event than the one named, becauseical addprints an ID that identifies no event. I lost a real calendar entry to this on v0.12.2 before working out what had happened.Reproduction
The test event is still there afterwards. Nothing in the output suggests the wrong event was targeted.
ical show 9933123D-95D5resolves the same way, so the problem is visible read-only too — it prints the details of some arbitrary event.Cause
Two things compound.
1.
ShortIDtruncates to 13 characters, which is never distinguishing.On macOS an event identifier is
<store UUID>:<event UUID>. The first component is shared by every event in the store, so the first 13 characters are identical across the whole calendar database.PrintCreatedEventandPrintUpdatedEventprint that prefix as the event's ID — the same string every time. The doc comment reasons that "two UUID segments … is enough to disambiguate events from the same source", but under this format the two segments are precisely the part that is not per-event.2.
findEventByPrefixtrusts a lookup that did not round-trip.EKEventStore.eventWithIdentifier:returns some event for a partial identifier instead of nil, soclient.Event("9933123D-95D5")succeeds and returns an arbitrary event. Because it succeeds, control never reaches the prefix-matching branch below — which would have reported the ambiguity correctly, since hundreds of events share that prefix.show,updateanddeleteall resolve their argument through this function, sodelete -facts on whatever came back.The same hazard applies to the row-number path a few lines above: a cached ID from a previous listing that has since been deleted resolves to an unrelated event rather than falling through to the search, so the "Event may have been deleted since listing" comment describes a fallback that cannot trigger.
Suggested fix
PR incoming. Two commits:
ShortID. The full ID is what--idneeds, and there is no correct truncation under this identifier format.The PR adds regression tests for the resolver behind a two-method interface, so they run without a live EventKit store. Three of them return an unrelated event against the current resolver.
Note on severity
This is silent and unrecoverable — macOS Calendar has no per-event trash, and the output gives no signal that the wrong event was hit. Anyone scripting
ical addand feeding the printed ID back toical deletewill eventually delete something else. Worth a patch release once fixed.