Skip to content

Fix: reminder fires at wrong time for IST (UTC+5:30) users#7066

Open
priyadarshiutkarsh wants to merge 2 commits intoBasedHardware:mainfrom
priyadarshiutkarsh:patch-1
Open

Fix: reminder fires at wrong time for IST (UTC+5:30) users#7066
priyadarshiutkarsh wants to merge 2 commits intoBasedHardware:mainfrom
priyadarshiutkarsh:patch-1

Conversation

@priyadarshiutkarsh
Copy link
Copy Markdown

Fixes #7059

Root cause: The LLM was asked to convert local time → UTC (subtract 5:30h for IST), but would output "5pm" with a Z suffix instead of "11:30am UTC", causing reminders to fire 5.5h late.

Fix: Have the LLM output local time with no suffix, and do the timezone conversion deterministically using ZoneInfo.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 28, 2026

Greptile Summary

This PR shifts timezone responsibility from the LLM to Python: the prompt now asks the model to output a naive local-time string, and extract_action_items converts it to UTC deterministically using ZoneInfo. The structural fix (prompt + post-processing) is correct and addresses the root cause for IST users.

  • The except Exception fallback on the ZoneInfo lookup is silent — if a client sends an unrecognised timezone string the reminder silently reverts to UTC-based scheduling with no log entry, making the regression invisible in production.

Confidence Score: 4/5

Safe to merge; the core fix is correct and the one remaining gap is a P2 observability issue in the error fallback path.

No P0 issues. One P2 finding (silent ZoneInfo exception with no log). The primary fix — moving UTC conversion from the LLM to deterministic Python code — is structurally sound, the indentation and stale-check logic are correct, and the fallback to UTC on ZoneInfo failure is a graceful degradation (just unobservable).

backend/utils/llm/conversation_processing.py — the silent except Exception block around ZoneInfo(tz) (lines 563–567).

Important Files Changed

Filename Overview
backend/utils/llm/conversation_processing.py Prompt updated to request naive local-time strings; post-processing now deterministically converts them to UTC via ZoneInfo. Core logic is sound; minor observability gap in the ZoneInfo exception fallback.

Sequence Diagram

sequenceDiagram
    participant LLM
    participant extract_action_items
    participant ZoneInfo

    Note over extract_action_items: started_at & current_time passed as UTC ISO strings
    extract_action_items->>LLM: Prompt: output due_at as LOCAL time, no suffix (e.g. "2025-10-04T10:00:00")
    LLM-->>extract_action_items: due_at = naive datetime (tzinfo=None)

    alt tzinfo is None (expected path)
        extract_action_items->>ZoneInfo: ZoneInfo(tz)
        alt valid timezone
            ZoneInfo-->>extract_action_items: user_tz
            extract_action_items->>extract_action_items: replace(tzinfo=user_tz).astimezone(UTC)
        else ZoneInfoNotFoundError (silent fallback)
            extract_action_items->>extract_action_items: replace(tzinfo=UTC)  ⚠️ no log
        end
    else tzinfo present (LLM ignored instructions)
        extract_action_items->>extract_action_items: .astimezone(UTC)
    end

    extract_action_items->>extract_action_items: stale check: due_at < now - 1 day → clear
    extract_action_items-->>extract_action_items: action_items (all due_at in UTC)
Loading

Reviews (2): Last reviewed commit: "Fix indentation in due_at timezone conve..." | Re-trigger Greptile

Comment on lines +569 to +571
else:
action_item.due_at = action_item.due_at.astimezone(timezone.utc)
if action_item.due_at < now - timedelta(days=1):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 else targets the wrong if, crashing on any None due date

The else on line 569 is indented to match the outer if action_item.due_at is not None: (line 562), not the inner if action_item.due_at.tzinfo is None: (line 563). This means when due_at is None the else branch fires and calls None.astimezone(timezone.utc), raising AttributeError. That exception bubbles out of the for loop and is caught by the outer except Exception as e: on line 580, causing extract_action_items to silently return [] for any conversation where even one action item has no due date — which is the common case.

Additionally, the stale-date check on line 571 is now outside the if action_item.due_at is not None: guard, so even if the else were fixed, None < now - timedelta(days=1) would raise TypeError.

The else branch should be one indent level deeper (matching the inner if tzinfo is None:) and the stale-date check should remain inside the outer guard:

            if action_item.due_at is not None:
                if action_item.due_at.tzinfo is None:
                    try:
                        user_tz = ZoneInfo(tz) if tz else timezone.utc
                        action_item.due_at = action_item.due_at.replace(tzinfo=user_tz).astimezone(timezone.utc)
                    except Exception:
                        action_item.due_at = action_item.due_at.replace(tzinfo=timezone.utc)
                else:
                    action_item.due_at = action_item.due_at.astimezone(timezone.utc)
                if action_item.due_at < now - timedelta(days=1):
                    logger.warning(
                        f'Clearing past due_at {action_item.due_at.isoformat()} for action item: {action_item.description}'
                    )
                    action_item.due_at = None

Comment on lines 525 to 526
Conversation started at: {started_at}
Current time: {current_time}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 LLM still receives UTC reference times but must reason in local time

started_at and current_time are passed as UTC ISO strings (e.g., "2025-10-03T13:25:00Z"), but the prompt now asks the LLM to resolve "today" / "tomorrow" in the user's local timezone without converting its output to UTC. The LLM still has to mentally shift REFERENCE_TIME from UTC to local time to determine the correct calendar date (e.g., 13:25 UTC = 18:55 IST, so "tomorrow" is Oct 4 IST, not Oct 3). Passing local-time reference values (pre-converted in Python) would make the LLM's job unambiguous and reduce the remaining surface for off-by-one-day errors near midnight boundaries.

The else branch and stale-date check were misindented, causing
AttributeError on action items without a due date and silently
returning an empty list for the entire conversation.
@priyadarshiutkarsh
Copy link
Copy Markdown
Author

@greptile-apps review

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.

Reminders fire at wrong time for UTC+5:30 (India) timezone

1 participant