Skip to content

feat: daily session reset by timezone#198

Open
americodias wants to merge 1 commit intoRichardAtCT:mainfrom
americodias:pr/daily-session-reset
Open

feat: daily session reset by timezone#198
americodias wants to merge 1 commit intoRichardAtCT:mainfrom
americodias:pr/daily-session-reset

Conversation

@americodias
Copy link
Copy Markdown

Summary

Adds two settings — SESSION_DAILY_RESET_HOUR (0-23) and SESSION_DAILY_RESET_TIMEZONE (IANA tz string) — that force session expiry at a recurring daily boundary. A session whose last_used falls before today's reset hour (in the configured local timezone) is treated as expired in addition to the existing SESSION_TIMEOUT_HOURS rule.

Why

The existing inactivity-based timeout works well for natural breaks but doesn't enforce a fresh start each day. Some users (myself included) prefer "every morning, the bot starts a fresh session" to avoid yesterday's working set leaking into today's first message — without needing to remember /new.

Two examples:

  • Personal/journaling use: each day's first message should be a clean slate. SESSION_DAILY_RESET_HOUR=3 + SESSION_DAILY_RESET_TIMEZONE=Europe/Lisbon ensures the 9am check-in starts fresh even if the previous session was at 11pm.
  • Team/operational use: similar reasoning for daily standups, incident handoffs.

What

src/claude/session.pyClaudeSession.is_expired() gains two optional parameters (daily_reset_hour, daily_reset_tz); existing (timeout_hours) calls remain valid (defaults preserve previous behavior). New SessionManager._is_session_expired(session) helper threads both rules through one call site.

src/claude/facade.py — two existing s.is_expired(self.config.session_timeout_hours) callsites switch to self.session_manager._is_session_expired(s) so they pick up the daily rule.

src/config/settings.py — adds the two new settings:

session_daily_reset_hour: Optional[int] = Field(
    default=None,  # disabled by default
    description="Hour of day (0-23) to force session reset. None = disabled.",
    ge=0, le=23,
)
session_daily_reset_timezone: str = Field(
    default="UTC",
    description="Timezone for daily reset hour (e.g. 'Europe/Lisbon')",
)

How it works

The logic is a lazy expiry check, not a background scheduler:

# (simplified)
now_local = datetime.now(UTC).astimezone(ZoneInfo(daily_reset_tz))
today_reset = now_local.replace(hour=daily_reset_hour, minute=0, second=0)
if now_local < today_reset:
    today_reset -= timedelta(days=1)
last_used_local = _to_utc(session.last_used).astimezone(tz)
if last_used_local < today_reset:
    return True  # expired

So a session created Tuesday 11pm and accessed Wednesday 9am — with reset at 3am Lisbon — is detected as expired the moment Wednesday's is_expired() runs. No timer, no race condition, no jobs to schedule.

Compatibility

  • Disabled by default (session_daily_reset_hour = None). Pure no-op when unset.
  • All existing is_expired(timeout_hours) callsites keep working — the new params are optional with sensible defaults.

Test plan

  • With reset disabled (default), behavior unchanged — sessions only expire on inactivity timeout
  • With SESSION_DAILY_RESET_HOUR=3 and TZ=Europe/Lisbon: session created 11pm, query at 9am next day → fresh session
  • Cross-DST transitions (zoneinfo handles correctly via ZoneInfo)
  • Cross-timezone deployment: bot running in UTC, SESSION_DAILY_RESET_TIMEZONE=America/New_York → reset boundary follows NYC, not UTC

Notes

Uses zoneinfo.ZoneInfo from stdlib (3.9+) — no new dependencies. The codebase already requires Python 3.11.

Adds SESSION_DAILY_RESET_HOUR and SESSION_DAILY_RESET_TIMEZONE settings.
A session whose last_used timestamp falls before today's reset hour (in
the configured local timezone) is treated as expired, in addition to the
existing inactivity-based timeout. Lazy check on next access — no
background task or scheduler needed.

Useful for forcing a fresh session each morning so the chat doesn't
carry yesterday's working set indefinitely. Disabled by default.
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