openclaw - 💡(How to fix) Fix `evaluateSessionFreshness` returns fresh-forever for future `updatedAt` timestamps (clock-skew edge case) [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#72989Fetched 2026-04-28 06:29:05
View on GitHub
Comments
1
Participants
2
Timeline
2
Reactions
0
Timeline (top)
closed ×1commented ×1

Fix Action

Fix / Workaround

resolveMergedUpdatedAt (src/config/sessions/types.ts:375) takes Math.max(existing.updatedAt, patch.updatedAt, options.now ?? Date.now()), so a caller passing patch.updatedAt = <future> will persist it. Likely sources: any path feeding inbound message timestamps (clock skew between the messaging platform and the openclaw host) into the merge. Once persisted, every subsequent evaluateSessionFreshness call against that entry returns fresh.

  1. Reject at merge boundary — in resolveMergedUpdatedAt, drop patch.updatedAt when it exceeds options.now ?? Date.now(). Stronger guarantee but more invasive — affects any caller that legitimately needs to project forward (e.g., test fixtures).

Code Example

// src/config/sessions/reset-policy.ts:85-86
const staleDaily = dailyResetAt != null && params.updatedAt < dailyResetAt;
const staleIdle = idleExpiresAt != null && params.now > idleExpiresAt;
// idleExpiresAt = updatedAt + idleMinutes * 60_000

---

const effectiveUpdatedAt = Math.min(params.updatedAt, params.now);
   const idleExpiresAt =
     params.policy.idleMinutes != null && params.policy.idleMinutes > 0
       ? effectiveUpdatedAt + params.policy.idleMinutes * 60_000
       : undefined;
   const staleDaily = dailyResetAt != null && effectiveUpdatedAt < dailyResetAt;
RAW_BUFFERClick to expand / collapse

evaluateSessionFreshness (src/config/sessions/reset-policy.ts:72-92) returns fresh: true for any session whose updatedAt is in the future relative to now, regardless of reset mode. This silently defeats both idle and daily reset gates for any caller using the result.

Code trace

// src/config/sessions/reset-policy.ts:85-86
const staleDaily = dailyResetAt != null && params.updatedAt < dailyResetAt;
const staleIdle = idleExpiresAt != null && params.now > idleExpiresAt;
// idleExpiresAt = updatedAt + idleMinutes * 60_000

For updatedAt > now:

  • staleDaily is false (updatedAt is after any past dailyResetAt).
  • idleExpiresAt is also in the future, so staleIdle = now > idleExpiresAt is false.
  • Result: fresh: true always, no matter how far back the configured reset would otherwise sit.

Where a future updatedAt can enter the store

resolveMergedUpdatedAt (src/config/sessions/types.ts:375) takes Math.max(existing.updatedAt, patch.updatedAt, options.now ?? Date.now()), so a caller passing patch.updatedAt = <future> will persist it. Likely sources: any path feeding inbound message timestamps (clock skew between the messaging platform and the openclaw host) into the merge. Once persisted, every subsequent evaluateSessionFreshness call against that entry returns fresh.

I haven't reproduced this in a real deployment — flagging as a clock-skew edge case worth guarding rather than a confirmed prod bug. Surfaced while reviewing #72901, where the same helper is now load-bearing for the Slack thread-history fetch gate.

Possible fixes

Two shapes:

  1. Clamp at evaluation time — inside evaluateSessionFreshness, treat updatedAt > now as now for staleness math. Localizes the change to the helper:

    const effectiveUpdatedAt = Math.min(params.updatedAt, params.now);
    const idleExpiresAt =
      params.policy.idleMinutes != null && params.policy.idleMinutes > 0
        ? effectiveUpdatedAt + params.policy.idleMinutes * 60_000
        : undefined;
    const staleDaily = dailyResetAt != null && effectiveUpdatedAt < dailyResetAt;
  2. Reject at merge boundary — in resolveMergedUpdatedAt, drop patch.updatedAt when it exceeds options.now ?? Date.now(). Stronger guarantee but more invasive — affects any caller that legitimately needs to project forward (e.g., test fixtures).

Happy to PR option 1 if a maintainer confirms this is worth guarding.

extent analysis

TL;DR

Clamp the updatedAt value to the current time when evaluating session freshness to prevent sessions with future update times from being considered fresh.

Guidance

  • Identify the source of the future updatedAt values, which could be due to clock skew between the messaging platform and the openclaw host, and consider synchronizing clocks or using a consistent time source.
  • Evaluate the trade-offs between clamping the updatedAt value at evaluation time (option 1) and rejecting it at the merge boundary (option 2), considering the potential impact on legitimate use cases that require projecting forward in time.
  • Test the proposed fix thoroughly to ensure it does not introduce unintended consequences, such as affecting sessions that are intentionally set to expire in the future.
  • Consider adding additional logging or monitoring to detect and alert on instances where updatedAt values are being set to future times, to help identify and address the root cause of the issue.

Example

const effectiveUpdatedAt = Math.min(params.updatedAt, params.now);
const idleExpiresAt =
  params.policy.idleMinutes != null && params.policy.idleMinutes > 0
    ? effectiveUpdatedAt + params.policy.idleMinutes * 60_000
    : undefined;
const staleDaily = dailyResetAt != null && effectiveUpdatedAt < dailyResetAt;

Notes

The proposed fix assumes that updatedAt values in the future are always an error and should be clamped to the current time. However, there may be legitimate use cases where sessions are intentionally set to expire in the future, and the fix should be carefully evaluated to ensure it does not introduce unintended consequences.

Recommendation

Apply the workaround by clamping the updatedAt value at evaluation time (option 1), as it is a more localized change and less likely to affect other parts of the system.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix `evaluateSessionFreshness` returns fresh-forever for future `updatedAt` timestamps (clock-skew edge case) [1 comments, 2 participants]