Skip to content

Conversation

@hubert-marek
Copy link
Owner

@hubert-marek hubert-marek commented Feb 1, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Graceful handling when event recurrence expansion fails
    • More consistent timezone-aware sorting and listing of events
    • Clearer validation errors for attendees missing emails
  • New Features

    • Watch/notification expiration accepts ISO 8601 dates and numeric milliseconds
    • Notification channels can opt into payload delivery
  • Tests

    • Updated calendar test fixtures and assertions to use explicit start/end datetime fields
  • Chores

    • Updated local ignore patterns and experiment/config tracking rules

✏️ Tip: You can customize this high-level summary in your review settings.

Artem Zhuravel added 3 commits January 31, 2026 19:14
Resolved multiple issues that were causing internal server errors (500)
in the Calendar API replica:

1. Import endpoint organizer handling
   - Fixed duplicate kwargs error when importing events with organizer field
   - Events now correctly preserve custom organizer email from imports
   - Organizer defaults to creator when not specified

2. Watch channel expiration parsing
   - Added parse_watch_expiration() to handle multiple formats:
     * Integer milliseconds since epoch
     * String milliseconds (1739627400000)
     * ISO 8601 date strings (2026-02-15T14:30:00Z)
   - Applied to all watch endpoints: events, calendarList, ACL, settings

3. Channel expiration storage
   - Changed Channel.expiration from Integer to BigInteger
   - Prevents PostgreSQL integer overflow for millisecond timestamps
   - Timestamps like 1893456000000 (year 2030) now store correctly

4. Attendees validation
   - Added email field validation for attendees in create/update
   - Returns proper 400 error with field location instead of 500 KeyError
   - Validates both POST and PATCH operations

These fixes address 129 of the 500 errors identified in benchmark testing,
with remaining errors being cascading failures that are now resolved.

Files changed:
- backend/src/services/calendar/api/methods.py
- backend/src/services/calendar/database/operations.py
- backend/src/services/calendar/database/schema.py
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

Walkthrough

Centralizes watch expiration parsing/validation, updates Channel.expiration to BigInteger and adds payload flag, refactors watch endpoints to use the new parser, adds per-attendee email validation and datetime normalization in event operations, and updates tests and .gitignore rules.

Changes

Cohort / File(s) Summary
Configuration
/.gitignore
Removed .env and .env.local from ignore list; added *.env.*, local_experiments/, and .claude/** ignores while explicitly tracking !.claude/CLAUDE.md and !.claude/settings.json; added .claude/settings.local.json to ignore.
API Layer
backend/src/services/calendar/api/methods.py
Added parse_watch_expiration(expiration: Any) -> Optional[int] to normalize/validate expiration values (None, int, numeric string, ISO 8601). Refactored calendar_list_watch, events_watch, acl_watch, and settings_watch to use it and pass millisecond expirations into Channel.
Database Schema
backend/src/services/calendar/database/schema.py
Changed Channel.expiration from Integer to BigInteger (ms since epoch) and added payload: bool field with default false.
Database Operations
backend/src/services/calendar/database/operations.py
create_event/update_event: per-attendee email validation using enumeration with indexed RequiredFieldError. list_events: added _normalize_dt() for consistent timezone-aware comparisons and sorting. get_event_instances: wrapped recurrence expansion in try/except to log and return empty results on failure.
Tests
examples/calendar/testsuites/calendar_bench.json
Replaced start_datetime/end_datetime with start.dateTime/end.dateTime, added calendar_event_attendees entries, expanded ignore lists for variable/computed fields, and adjusted time zone/date references and metadata in multiple test blocks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/src/services/calendar/database/schema.py (1)

633-639: ⚠️ Potential issue | 🟠 Major

Create a migration to change calendar_channels.expiration from Integer to BigInteger.

The expiration column is currently defined as Integer in the database (from initial migration 2ac6c433442a) but the schema now declares it as BigInteger. Without a migration to alter this column type, there will be a runtime schema mismatch. The payload column already exists in the initial migration and requires no changes.

🤖 Fix all issues with AI agents
In @.gitignore:
- Around line 244-247: The negated patterns !.claude/CLAUDE.md and
!.claude/settings.json won’t work because the parent directory is ignored by an
earlier `.claude` entry and by `.claude/**`; update the ignore rules so the
directory itself is unignored (add `!.claude/` before the specific file
exceptions) or remove the earlier `.claude` ignore, and keep the `.claude/**`
rule only if you still explicitly unignore the directory with `!.claude/` so the
negations for `!.claude/CLAUDE.md` and `!.claude/settings.json` take effect.

In `@backend/src/services/calendar/api/methods.py`:
- Around line 1099-1109: The Channel instantiation uses an unnecessary f-string
for resource_uri (resource_uri=f"/users/me/calendarList") which triggers Ruff
F541; change the f-string to a plain string literal
(resource_uri="/users/me/calendarList") inside the Channel(...) call where
resource_id is created by generate_resource_id() and expiration is set via
parse_watch_expiration(body.get("expiration")), leaving all other fields
(id=channel_id, type=channel_type, address=address, token=body.get("token"))
unchanged.
- Around line 108-168: parse_watch_expiration currently calls parse_rfc3339 and
uses dt.timestamp(), which treats naive datetimes as local time; update
parse_watch_expiration to detect a naive datetime returned by parse_rfc3339
(dt.tzinfo is None) and replace its tzinfo with datetime.timezone.utc (or call
dt.replace(tzinfo=timezone.utc)) before calling timestamp(), so ISO strings
without zone are normalized to UTC prior to converting to milliseconds; keep
existing int conversion and ValidationError handling intact.

In `@backend/src/services/calendar/database/operations.py`:
- Around line 1043-1057: The current _normalize_dt(dt) maps None to datetime.min
causing all-day events (which have start_datetime=None but start_date set) to
sort to the top; change the sorting to use the event's start_date as a fallback
when start_datetime is None by adjusting the sort key (or _normalize_dt) to
accept the event (or both dt and the event) and, if dt is None, convert
event.start_date to a timezone-aware datetime at the start of day (timezone.utc)
and return that; keep the existing behavior for updated_at sorting (only use
updated_at conversion) and continue using e.id as the tiebreaker.

In `@examples/calendar/testsuites/calendar_bench.json`:
- Around line 5669-5671: The test JSON contains a redundant/mismatched assertion
where the "start" field is being used to assert an end date value; specifically
remove or correct the duplicate/misplaced assertion so that only the
"end.dateTime" assertion contains "2018-08-23" (or change the "start" assertion
to assert the actual start date if that was intended). Update the assertions
referencing "start" and "end.dateTime" in the calendar_bench JSON so they each
validate the appropriate date field consistently.
🧹 Nitpick comments (1)
examples/calendar/testsuites/calendar_bench.json (1)

701-714: Extensive ignore list may mask unintended changes.

This ignore list excludes most event fields (attendees, color_id, description, end, recurrence, reminders, start, status, summary, transparency, visibility), leaving only location changes to be verified. While this focuses the assertion on the intended change, it could mask regressions in other fields.

Consider documenting why such an extensive ignore list is necessary here, or verify this is intentional for this specific test scenario.

When parse_rfc3339 returns a naive datetime (no timezone info),
explicitly set tzinfo to UTC before calling timestamp(). This
ensures consistent behavior regardless of server timezone and
matches the pattern used in format_rfc3339.

Co-authored-by: Hubert <hubert-marek@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@backend/src/services/calendar/api/methods.py`:
- Around line 137-139: The current isinstance(expiration, int) check accepts
booleans because bool subclasses int; update the check in the
expiration-handling block (the one that currently reads "if
isinstance(expiration, int): return expiration") to explicitly exclude bools so
True/False are not treated as ints (e.g., use "if isinstance(expiration, int)
and not isinstance(expiration, bool): return expiration" or "if type(expiration)
is int: return expiration") so boolean values fall through to the existing
ValidationError path around line 170.

Comment on lines +137 to +139
# If already an int, return as-is
if isinstance(expiration, int):
return expiration
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n backend/src/services/calendar/api/methods.py | sed -n '120,160p'

Repository: hubert-marek/agent-diff

Length of output: 1592


🏁 Script executed:

cat -n backend/src/services/calendar/api/methods.py | sed -n '160,180p'

Repository: hubert-marek/agent-diff

Length of output: 838


Reject boolean expirations explicitly.

bool is a subclass of int in Python, so passing True or False will be accepted and stored as 1 or 0 milliseconds (representing 1970-01-01 00:00:00.001 UTC and Unix epoch respectively). These are not valid expiration times for calendar watch subscriptions. Tighten the isinstance check to reject booleans explicitly so they fall through to the existing ValidationError at line 170.

🔧 Suggested fix
-    if isinstance(expiration, int):
+    if isinstance(expiration, int) and not isinstance(expiration, bool):
         return expiration
🤖 Prompt for AI Agents
In `@backend/src/services/calendar/api/methods.py` around lines 137 - 139, The
current isinstance(expiration, int) check accepts booleans because bool
subclasses int; update the check in the expiration-handling block (the one that
currently reads "if isinstance(expiration, int): return expiration") to
explicitly exclude bools so True/False are not treated as ints (e.g., use "if
isinstance(expiration, int) and not isinstance(expiration, bool): return
expiration" or "if type(expiration) is int: return expiration") so boolean
values fall through to the existing ValidationError path around line 170.

@hubert-marek hubert-marek merged commit e7bf925 into main Feb 1, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant