Skip to content

fix(memory): replace deprecated datetime.utcnow() across memory subsystem#5839

Open
Ghraven wants to merge 1 commit into
crewAIInc:mainfrom
Ghraven:fix/memory-deprecated-utcnow
Open

fix(memory): replace deprecated datetime.utcnow() across memory subsystem#5839
Ghraven wants to merge 1 commit into
crewAIInc:mainfrom
Ghraven:fix/memory-deprecated-utcnow

Conversation

@Ghraven
Copy link
Copy Markdown

@Ghraven Ghraven commented May 17, 2026

Summary

datetime.utcnow() was deprecated in Python 3.12 (PEP 2038) and is scheduled for removal in Python 3.15. It also returns a naive UTC datetime, which is a common source of TypeError: can't compare offset-naive and offset-aware datetimes once other parts of the codebase start using timezone-aware datetimes.

This PR replaces all occurrences across the memory subsystem (4 files, 9 sites) with datetime.now(timezone.utc).

Files changed

File Sites Notes
memory/types.py 3 MemoryRecord.created_at / last_accessed default_factory, plus age_seconds calculation
memory/encoding_flow.py 1 now = datetime.utcnow() in encoding helper
memory/unified_memory.py 1 now = datetime.utcnow() in cleanup method
memory/storage/lancedb_storage.py 4 created_at, last_accessed, and two .isoformat() timestamp writes

Verification

All four files compile cleanly:

$ python -m py_compile lib/crewai/src/crewai/memory/types.py ...

No remaining utcnow in the memory package:

$ grep -rn utcnow lib/crewai/src/crewai/memory/
(no matches)

Happy to extend this to other parts of the codebase (streaming.py, tools/structured_tool.py, etc.) as a follow-up if you'd prefer to keep each PR focused.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed datetime handling in the memory system to use timezone-aware UTC timestamps consistently across all memory records, updates, and storage operations, improving reliability and consistency of timestamp management throughout the system.

Review Change Stack

…stem

datetime.utcnow() was deprecated in Python 3.12 and is scheduled for
removal in Python 3.15. It returns a naive UTC datetime, which also
causes TypeError when compared against timezone-aware datetimes.

Replace all occurrences in the memory subsystem (4 files, 9 sites)
with datetime.now(timezone.utc):

- memory/types.py: MemoryRecord.created_at/last_accessed default_factory
  and age_seconds calculation
- memory/encoding_flow.py: now = datetime.utcnow() in encoding helper
- memory/unified_memory.py: now = datetime.utcnow() in cleanup method
- memory/storage/lancedb_storage.py: created_at, last_accessed, and
  two isoformat() timestamp writes

All four files compile cleanly. No behaviour change for new records;
existing stored naive timestamps remain readable since we only changed
how new timestamps are produced.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

The PR standardizes datetime handling across the memory module by replacing all naive UTC timestamps (datetime.utcnow()) with timezone-aware UTC (datetime.now(timezone.utc)). This change applies to type defaults, storage operations, timestamp calculations, and record update flows across four files.

Changes

Memory module timezone standardization

Layer / File(s) Summary
MemoryRecord timestamp defaults and scoring
lib/crewai/src/crewai/memory/types.py
MemoryRecord.created_at and MemoryRecord.last_accessed default factories are updated to use datetime.now(timezone.utc). The compute_composite_score function's age calculation also switches to timezone-aware UTC for recency scoring.
LanceDB storage backend timestamp updates
lib/crewai/src/crewai/memory/storage/lancedb_storage.py
Schema placeholder creation, the _parse_dt fallback for missing dates, and touch_records's last_accessed computation all switch from naive UTC to datetime.now(timezone.utc).isoformat().
Encoding flow and unified memory updates
lib/crewai/src/crewai/memory/encoding_flow.py, lib/crewai/src/crewai/memory/unified_memory.py
The encoding pipeline's record update and unified memory's update() method now use timezone-aware UTC when setting last_accessed.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 Tick-tock, time grows wise,
No more naive UTC skies,
Timezone-aware, bright and clear,
Timestamps now precise, my dear!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: replacing deprecated datetime.utcnow() with datetime.now(timezone.utc) across the memory subsystem.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (1)
lib/crewai/src/crewai/memory/storage/lancedb_storage.py (1)

268-274: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize parsed datetimes to UTC-aware in _parse_dt.

Lines 272 and 274 can return naive datetimes. Line 272 directly returns datetime objects that may lack timezone info, and line 274 can return naive datetimes when input strings lack timezone information (i.e., strings not ending with "Z" or timezone suffix). This causes a TypeError during datetime comparisons and sorting operations (e.g., records.sort(key=lambda r: r.created_at) or comparisons like dt < oldest), especially when mixing legacy naive values with new UTC-aware datetimes written by this PR.

Proposed fix
         def _parse_dt(val: Any) -> datetime:
             if val is None:
                 return datetime.now(timezone.utc)
             if isinstance(val, datetime):
-                return val
+                return (
+                    val
+                    if val.tzinfo is not None
+                    else val.replace(tzinfo=timezone.utc)
+                )
             s = str(val)
-            return datetime.fromisoformat(s.replace("Z", "+00:00"))
+            dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
+            return dt if dt.tzinfo is not None else dt.replace(tzinfo=timezone.utc)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/memory/storage/lancedb_storage.py` around lines 268 -
274, The _parse_dt helper may return naive datetimes causing TypeError on
comparisons; ensure every branch returns a UTC-aware datetime by normalizing
values to timezone.utc: when val is a datetime, if dt.tzinfo is None set
tzinfo=timezone.utc (or convert via astimezone(timezone.utc)); when parsing
strings (after the "Z" -> "+00:00" replacement and datetime.fromisoformat),
check the parsed object's tzinfo and set it to timezone.utc if missing; keep the
None case returning datetime.now(timezone.utc). Update _parse_dt, and reference
datetime.fromisoformat, _parse_dt, and timezone.utc in your changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/crewai/src/crewai/memory/types.py`:
- Around line 371-372: The subtraction fails for naive datetimes in the age
calculation; before computing age_seconds and age_days, normalize
record.created_at to UTC if it's naive by checking record.created_at.tzinfo is
None and, in that case, set record.created_at =
record.created_at.replace(tzinfo=timezone.utc) (leave it unchanged if already
aware), then compute age_seconds = (datetime.now(timezone.utc) -
record.created_at).total_seconds() and age_days = max(age_seconds / 86400.0,
0.0).

---

Outside diff comments:
In `@lib/crewai/src/crewai/memory/storage/lancedb_storage.py`:
- Around line 268-274: The _parse_dt helper may return naive datetimes causing
TypeError on comparisons; ensure every branch returns a UTC-aware datetime by
normalizing values to timezone.utc: when val is a datetime, if dt.tzinfo is None
set tzinfo=timezone.utc (or convert via astimezone(timezone.utc)); when parsing
strings (after the "Z" -> "+00:00" replacement and datetime.fromisoformat),
check the parsed object's tzinfo and set it to timezone.utc if missing; keep the
None case returning datetime.now(timezone.utc). Update _parse_dt, and reference
datetime.fromisoformat, _parse_dt, and timezone.utc in your changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ab9a2d0-9c6e-402a-b157-f46c58f4164c

📥 Commits

Reviewing files that changed from the base of the PR and between a95d267 and afb49f5.

📒 Files selected for processing (4)
  • lib/crewai/src/crewai/memory/encoding_flow.py
  • lib/crewai/src/crewai/memory/storage/lancedb_storage.py
  • lib/crewai/src/crewai/memory/types.py
  • lib/crewai/src/crewai/memory/unified_memory.py

Comment on lines +371 to 372
age_seconds = (datetime.now(timezone.utc) - record.created_at).total_seconds()
age_days = max(age_seconds / 86400.0, 0.0)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# Find and examine the target file
fd -t f "types.py" lib/crewai/src/crewai/memory/ && cat -n lib/crewai/src/crewai/memory/types.py | sed -n '365,380p'

Repository: crewAIInc/crewAI

Length of output: 808


🏁 Script executed:

# Check if there are other references to created_at in the memory module
rg "created_at" lib/crewai/src/crewai/memory/ -n

Repository: crewAIInc/crewAI

Length of output: 2720


🏁 Script executed:

# Look for tzinfo checks or timezone imports in the types.py file
rg "tzinfo|timezone" lib/crewai/src/crewai/memory/types.py -n

Repository: crewAIInc/crewAI

Length of output: 315


🏁 Script executed:

# Find _parse_dt function definition
rg "_parse_dt" lib/crewai/src/crewai/memory/ -A 10 -B 2

Repository: crewAIInc/crewAI

Length of output: 5433


🏁 Script executed:

# Verify that datetime operations with mixed aware/naive raise TypeError
python3 << 'EOF'
from datetime import datetime, timezone

# Test: aware - naive = TypeError
aware = datetime.now(timezone.utc)
naive = datetime.now()

try:
    result = aware - naive
    print(f"No error (unexpected): {result}")
except TypeError as e:
    print(f"TypeError raised (expected): {e}")
EOF

Repository: crewAIInc/crewAI

Length of output: 144


🏁 Script executed:

# Check if there's any existing timezone handling in compute_composite_score
rg "compute_composite_score" lib/crewai/src/crewai/memory/types.py -B 5 -A 20

Repository: crewAIInc/crewAI

Length of output: 1975


Handle naive created_at before age math.

Line 371 raises TypeError: can't subtract offset-naive and offset-aware datetimes when record.created_at is timezone-naive, which occurs with records from qdrant storage or legacy data. This breaks recall scoring when processing mixed datasets.

Proposed fix
 def compute_composite_score(
@@
-    age_seconds = (datetime.now(timezone.utc) - record.created_at).total_seconds()
+    created_at = record.created_at
+    if created_at.tzinfo is None:
+        created_at = created_at.replace(tzinfo=timezone.utc)
+    age_seconds = (datetime.now(timezone.utc) - created_at).total_seconds()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/memory/types.py` around lines 371 - 372, The
subtraction fails for naive datetimes in the age calculation; before computing
age_seconds and age_days, normalize record.created_at to UTC if it's naive by
checking record.created_at.tzinfo is None and, in that case, set
record.created_at = record.created_at.replace(tzinfo=timezone.utc) (leave it
unchanged if already aware), then compute age_seconds =
(datetime.now(timezone.utc) - record.created_at).total_seconds() and age_days =
max(age_seconds / 86400.0, 0.0).

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