Skip to content

[Bug]: ICS calendar events are silently dropped when a property parameter is quoted and contains a delimiter #2313

Closed

Description

@benschifman

Bug Description

Note: I worked with claude to figure out why a calendar event was not displaying, and this is the bug it found. The bug report is generated by claude.

What happened

A confirmed 4-hour meeting on my Google Calendar never appeared anywhere in TaskNotes — not in the calendar view, not in icsSubscriptionService.getAllEvents(). No error was surfaced, and lastError for the subscription was empty. I only found it by diffing the raw .ics feed against the plugin's cache.

Of 1,323 VEVENTs in my feed, 697 of 854 distinct UIDs were cached. Restricting to events in the future (where nothing is pruned), 21 events, 20 cached, 1 missing.

The one missing future event is the only one of the 21 carrying a quoted parameter value that contains a delimiter character:

ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=someone@example.org;X-NUM-GUESTS=0;X-RESPONSE-COMMENT="Appreciate the invite but Kate and I are hosting our closing event all day on the 14th so won't be able to make it. Not sure of Dan's availability but you could add him and see if he is free.":mailto:someone@example.org
Note the escaped semicolons (;) inside the double-quoted parameter value. Google Calendar emits this whenever an invitee declines with a free-text comment containing a semicolon or apostrophe-escaped text.

Per RFC 5545 §3.2, a quoted param value is delimited by DQUOTE, so ; and : inside it must not be treated as separators. If the ICS line parser splits on ;/: without tracking quote state, this line shreds into garbage parameters and — going by the observed behaviour — takes the entire VEVENT down with it.

Why it matters
The failure is completely silent, and the trigger is another person's RSVP comment. Nothing the calendar owner does can cause or prevent it, and there is no signal that an event is missing. In my case a 41-attendee, 4-hour convening was invisible for weeks; I planned around an empty slot that was not empty.

Steps to Reproduce

Steps to reproduce
Subscribe to an ICS feed containing a VEVENT like the one below.
Refresh the subscription and wait for the cache to settle.
The event does not appear in the calendar view or getAllEvents().
BEGIN:VEVENT
UID:repro-quoted-param@example.com
DTSTART:20260914T140000Z
DTEND:20260914T180000Z
SUMMARY:Should appear but does not
STATUS:CONFIRMED
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=a@example.com;X-RESPONSE-COMMENT="Sorry; I have a conflict":mailto:a@example.com
END:VEVENT
Removing only the X-RESPONSE-COMMENT parameter should make the event appear.

Expected
The event is parsed and displayed. Unparseable parameters should at worst be ignored, never discard the whole VEVENT — and a parse failure should be surfaced rather than silent.

Caveat on the diagnosis
I identified this by correlation on real data (1 of 1 affected, 20 unaffected controls) plus the RFC reading. I have not stepped through the parser to confirm the exact failure point, and I have not run the minimal repro above against a clean vault. The observation that a specific future event is reproducibly absent from a settled cache is solid; the proposed mechanism is my best inference.

TaskNotes Debug Info

SYSTEM INFO:
	Obsidian version: 1.13.7
	Installer version: 1.12.7
	Operating system: Darwin Kernel Version 25.6.0: Fri Jul 31 19:11:03 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T8132 25.6.0
	Login status: not logged in
	Language: en
	Insider build toggle: off
	Live preview: on
	Base theme: dark
	Community theme: AnuPpuccin 1.5.0
	Snippets enabled: 1
	Restricted mode: off
	Plugins installed: 9
	Plugins enabled: 9
		1: Templater v2.25.0
		2: PDF++ v0.40.31
		3: Image Converter v1.4.6
		4: Periodic Notes v0.0.17
		5: TaskNotes v4.12.5
		6: Granola Meetings Simple Sync v2.2.1
		7: Notebook Navigator v3.3.7
		8: TaskNotes Workflows v0.1.4
		9: Omnisearch v1.31.0

Affected Task Frontmatter

Relevant Bases File Content

Relevant Settings or Customizations

No response

Console Errors or Logs


Screenshots or Screen Recording

No response

Activity

  1. callumalpass commented on Sep 13, 2026

    @callumalpass
    Owner

    Thanks @benschifman. I reproduced the missing event with your example on TaskNotes 4.12.5, but the quoted comment does parse successfully. Removing only X-RESPONSE-COMMENT still leaves the event hidden; changing PARTSTAT=DECLINED to ACCEPTED makes it appear.

    The current ICS import hides an event if any attendee is marked declined, without checking whether that attendee is the calendar owner. That can hide a meeting because another guest declined it.

    Could you confirm whether the declined attendee is someone other than the calendar owner? If possible, please share a minimal sanitized VEVENT preserving the ORGANIZER and ATTENDEE parameters and indicate which anonymized attendee is the owner. Please do not share the private calendar URL, access tokens, or the full calendar. This will help distinguish another guest's decline from an event the owner actually declined.

  2. benschifman commented on Sep 13, 2026

    @benschifman
    Author

    Thank you. (And as an aside, thanks for creating Tasknotes, which I use every day and get great value from!) I fed your response to claude in the session where I was looking into why some events were off, claude says:

    Thanks for reproducing this so quickly — and you're right, my diagnosis of the mechanism was wrong. I found the quoted X-RESPONSE-COMMENT by correlation and inferred a parser problem; the real correlate is that guests who decline tend to leave comments. Apologies for pointing you at the wrong line.

    Confirmed: the declined attendees are all other guests, not the calendar owner. Details from the live feed owner is masked below as owner@example.com):

    Event Owner PARTSTAT Declined attendees Visible in TaskNotes?
    4-hour meeting, 9/14 ACCEPTED 6 of 40 guests ❌ hidden
    Team meeting, 9/9 ACCEPTED 3 of 14 guests ❌ hidden
    Recurring team meeting, 10/21 DECLINED (owner) 4, incl. owner ❌ hidden — correctly

    I also ran it across every future event in the feed: every event with a non-owner DECLINED is missing from the cache; every event with no declines is present. So your reading — "hides the event if any attendee declined, without checking whether that attendee is the owner" — matches the data exactly.

    The practical effect is worth spelling out: on a shared team calendar, any meeting where one colleague can't make it disappears for everyone else. The two hidden events above are a 40-person meeting and a routine team meeting; in both cases the owner had accepted.

    Minimal sanitized VEVENT

    Structure and parameters are verbatim from the real event; emails, CNs, the comment text and UID are replaced. owner@example.com is the calendar owner (the account whose private ICS URL is subscribed). Only guest-declined and guest-declined-2 have declined.

    BEGIN:VEVENT
    DTSTART:20260914T140000Z
    DTEND:20260914T180000Z
    DTSTAMP:20260910T194803Z
    ORGANIZER;CN=organizer@example.com:mailto:organizer@example.com
    UID:sanitized-guest-decline-repro@example.com
    ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=owner@example.com;X-NUM-GUESTS=0:mailto:owner@example.com
    ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=guest-declined@example.com;X-NUM-GUESTS=0;X-RESPONSE-COMMENT="Sorry\; I have a conflict that day":mailto:guest-declined@example.com
    ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=guest-declined-2@example.com;X-NUM-GUESTS=0:mailto:guest-declined-2@example.com
    ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=guest-accepted@example.com;X-NUM-GUESTS=0:mailto:guest-accepted@example.com
    ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=guest-pending@example.com;X-NUM-GUESTS=0:mailto:guest-pending@example.com
    X-GOOGLE-CONFERENCE:https://meet.google.com/xxx-xxxx-xxx
    CREATED:20260623T185924Z
    LAST-MODIFIED:20260910T134810Z
    LOCATION:Example venue\, Washington\, DC
    SEQUENCE:0
    STATUS:CONFIRMED
    SUMMARY:Sanitized repro — guest declined, owner accepted
    TRANSP:OPAQUE
    END:VEVENT

    Expected: visible, since the owner accepted. Actual on 4.12.5: hidden.

    One design note, in case it's useful: Google's private ICS export doesn't carry an explicit "this is the subscriber" marker, but the feed's X-WR-CALNAME is the owner's address (bowner@example.com in my case), so matching the owner's ATTENDEE line against that is one way to find it. Where no attendee matches the owner at all — e.g. the owner created the event and is the ORGANIZER — there's nobody's decline to honour and the event should just show.

  3. callumalpass commented on Sep 21, 2026

    @callumalpass
    Owner

    Resolved in 4.13.1. Thanks @benschifman for reporting this. I’m closing issue #2313.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions