Skip to content

fix: replace deprecated datetime.utcnow() with datetime.now(timezone.utc) in memory module#5877

Open
Ghraven wants to merge 2 commits into
crewAIInc:mainfrom
Ghraven:fix/deprecate-datetime-utcnow
Open

fix: replace deprecated datetime.utcnow() with datetime.now(timezone.utc) in memory module#5877
Ghraven wants to merge 2 commits into
crewAIInc:mainfrom
Ghraven:fix/deprecate-datetime-utcnow

Conversation

@Ghraven
Copy link
Copy Markdown

@Ghraven Ghraven commented May 21, 2026

Summary

Replace all datetime.utcnow() calls with datetime.now(timezone.utc) in the memory module to resolve the Python 3.12 deprecation warning.

Problem

datetime.utcnow() has been deprecated since Python 3.12 because it returns a naive datetime object that does not carry timezone information, making it ambiguous and error-prone when comparing with timezone-aware timestamps.

Before (deprecated):

datetime.utcnow()  # Returns naive datetime, emits DeprecationWarning on 3.12+

After (recommended):

datetime.now(timezone.utc)  # Returns timezone-aware UTC datetime

Changes

File Changes
types.py Updated default_factory for created_at and last_accessed fields; updated compute_composite_score()
lancedb_storage.py Updated 4 occurrences in save(), _now(), and touch_records()
encoding_flow.py Updated 1 occurrence in execute_plans()
unified_memory.py Updated 1 occurrence in update()
test_unified_memory.py Updated test assertions to use datetime.now(timezone.utc)

Verification

  • All changes are mechanical replacements with no behavioral difference
  • datetime.now(timezone.utc) returns the exact same instant as datetime.utcnow(), but as a timezone-aware object
  • Import updated from from datetime import datetime to from datetime import datetime, timezone in all affected files
  • Grep confirms no remaining utcnow calls in the memory module

Summary by CodeRabbit

  • Bug Fixes
    • Memory timestamps (creation, last-access, and recency scoring) now consistently use timezone-aware UTC, preventing inconsistent time calculations and improving ordering/decay accuracy.
  • Tests
    • Added tests to ensure legacy and varied timestamp formats are normalized to UTC so memory behavior remains consistent.

Review Change Stack

…utc)

datetime.utcnow() has been deprecated since Python 3.12 because it
returns a naive datetime that can be ambiguous. Replace all usages in
the memory module with datetime.now(timezone.utc), which returns a
timezone-aware datetime object.

Files changed:
- lib/crewai/src/crewai/memory/types.py
- lib/crewai/src/crewai/memory/storage/lancedb_storage.py
- lib/crewai/src/crewai/memory/encoding_flow.py
- lib/crewai/src/crewai/memory/unified_memory.py
- lib/crewai/tests/memory/test_unified_memory.py
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

This PR migrates the crewAI memory subsystem from naive UTC timestamps (datetime.utcnow()) to timezone-aware UTC timestamps (datetime.now(timezone.utc)). The change is applied consistently across type definitions, storage operations, encoding/update flows, and the test suite.

Changes

Timezone-aware UTC datetime migration

Layer / File(s) Summary
Memory record type defaults and recency scoring
lib/crewai/src/crewai/memory/types.py
MemoryRecord.created_at and MemoryRecord.last_accessed now default to timezone-aware UTC using lambda factories instead of datetime.utcnow. Scoring recency calculations in compute_composite_score also use timezone-aware datetime.now(timezone.utc) for age computation.
LanceDB storage timezone-aware timestamps
lib/crewai/src/crewai/memory/storage/lancedb_storage.py
Schema placeholder rows, missing datetime defaults during record parsing, and touch/access record updates all switch to timezone-aware UTC with ISO serialization and parsed datetimes are normalized to timezone.utc.
Encoding and memory update flows
lib/crewai/src/crewai/memory/encoding_flow.py, lib/crewai/src/crewai/memory/unified_memory.py
Record encoding and memory module updates to last_accessed during plan execution and explicit memory updates now use timezone-aware UTC timestamps.
Composite memory scoring test suite & LanceDB parsing test
lib/crewai/tests/memory/test_unified_memory.py
Test scenarios updated to construct records with timezone-aware created_at timestamps; added test verifies legacy storage timestamp strings are normalized to UTC-aware datetimes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 I nibble timestamps in fields so small,
swap naive ticks for UTC-aware call.
Now memories march with timezone grace,
each moment stamped in its proper place.
Hoppity hop — time keeps steady pace!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 accurately describes the main change: replacing deprecated datetime.utcnow() with datetime.now(timezone.utc) across the memory module to address Python 3.12 deprecation warnings.
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.

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-275: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Normalize legacy naive timestamps in _row_to_record’s _parse_dt to UTC-aware datetimes.

In lib/crewai/src/crewai/memory/storage/lancedb_storage.py (lines 268-275), _parse_dt returns parsed datetimes as-is; legacy created_at/last_accessed strings saved without an offset produce naive datetimes. Later code (e.g., datetime.now(timezone.utc) - record.created_at in scoring) can raise TypeError: offset-naive and offset-aware. Coerce tzinfo is None values to timezone.utc and normalize aware values to UTC before returning.

Suggested fix
     def _row_to_record(self, row: dict[str, Any]) -> MemoryRecord:
         def _parse_dt(val: Any) -> datetime:
             if val is None:
                 return datetime.now(timezone.utc)
-            if isinstance(val, datetime):
-                return val
-            s = str(val)
-            return datetime.fromisoformat(s.replace("Z", "+00:00"))
+            if isinstance(val, datetime):
+                dt = val
+            else:
+                s = str(val)
+                dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
+            if dt.tzinfo is None:
+                return dt.replace(tzinfo=timezone.utc)
+            return dt.astimezone(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 -
275, In _row_to_record’s helper _parse_dt, ensure any parsed datetime is
timezone-aware in UTC: if val is None return datetime.now(timezone.utc); if val
is a datetime and tzinfo is None set tzinfo=timezone.utc; if the datetime is
aware convert it to UTC (astimezone(timezone.utc)); when parsing strings,
produce an aware UTC datetime (e.g., after fromisoformat ensure tzinfo is not
None and normalize to UTC). Update _parse_dt so created_at and last_accessed are
always UTC-aware datetimes to avoid offset-naive vs offset-aware errors.
🤖 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.

Outside diff comments:
In `@lib/crewai/src/crewai/memory/storage/lancedb_storage.py`:
- Around line 268-275: In _row_to_record’s helper _parse_dt, ensure any parsed
datetime is timezone-aware in UTC: if val is None return
datetime.now(timezone.utc); if val is a datetime and tzinfo is None set
tzinfo=timezone.utc; if the datetime is aware convert it to UTC
(astimezone(timezone.utc)); when parsing strings, produce an aware UTC datetime
(e.g., after fromisoformat ensure tzinfo is not None and normalize to UTC).
Update _parse_dt so created_at and last_accessed are always UTC-aware datetimes
to avoid offset-naive vs offset-aware errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 31135e58-7402-4de0-a5af-32ebce4ab811

📥 Commits

Reviewing files that changed from the base of the PR and between 418afd2 and 68ad3e4.

📒 Files selected for processing (5)
  • 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
  • lib/crewai/tests/memory/test_unified_memory.py

@Ghraven
Copy link
Copy Markdown
Author

Ghraven commented May 21, 2026

Thanks for the review. I addressed the timestamp normalization issue by coercing legacy naive datetimes to UTC and normalizing aware datetimes back to UTC before building MemoryRecords. I also added a regression test covering both an offset-less legacy timestamp and an offset-aware timestamp.\n\nLocal verification: python -m compileall -q lib/crewai/src/crewai/memory/storage/lancedb_storage.py lib/crewai/tests/memory/test_unified_memory.py and git diff --check pass. I was not able to run the focused pytest locally because this lightweight checkout/environment is missing project test dependencies (json_repair after disabling unavailable default pytest plugins), but the test is included for CI to run in the normal repo environment.

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)

535-546: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize created_at in get_scope_info() before comparison.

get_scope_info() can compare naive and aware datetimes directly (Line 543/Line 545), which will raise TypeError on mixed legacy/new rows. Reuse the same UTC-normalization path used by _row_to_record before min/max comparisons.

Suggested fix
@@
-            created = row.get("created_at")
-            if created:
-                dt = (
-                    datetime.fromisoformat(str(created).replace("Z", "+00:00"))
-                    if isinstance(created, str)
-                    else created
-                )
-                if isinstance(dt, datetime):
-                    if oldest is None or dt < oldest:
-                        oldest = dt
-                    if newest is None or dt > newest:
-                        newest = dt
+            created = row.get("created_at")
+            if created:
+                if isinstance(created, datetime):
+                    dt = created
+                else:
+                    dt = datetime.fromisoformat(str(created).replace("Z", "+00:00"))
+                dt = dt.replace(tzinfo=timezone.utc) if dt.tzinfo is None else dt.astimezone(timezone.utc)
+                if oldest is None or dt < oldest:
+                    oldest = dt
+                if newest is None or dt > newest:
+                    newest = dt
🤖 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 535 -
546, Normalize the created_at value in get_scope_info() before comparing to
oldest/newest: reuse the same UTC-normalization logic used in _row_to_record by
parsing string timestamps the same way and converting any naive datetime to an
aware UTC datetime (so all datetimes are timezone-aware) before doing the
min/max comparisons; update the handling of the local variable created (and dt)
so oldest and newest are compared against consistently UTC-aware datetimes
instead of mixing naive and aware datetimes.
🤖 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/tests/memory/test_unified_memory.py`:
- Around line 147-148: The test currently asserts equality of record.created_at
and record.last_accessed but doesn't ensure their tzinfo is normalized to
timezone.utc; update the normalization test to explicitly assert that
record.created_at.tzinfo is timezone.utc and that record.last_accessed.tzinfo is
timezone.utc (referencing the variables record.created_at and
record.last_accessed in the test_unified_memory.py test) so the contract
enforces UTC-aware datetimes.

---

Outside diff comments:
In `@lib/crewai/src/crewai/memory/storage/lancedb_storage.py`:
- Around line 535-546: Normalize the created_at value in get_scope_info() before
comparing to oldest/newest: reuse the same UTC-normalization logic used in
_row_to_record by parsing string timestamps the same way and converting any
naive datetime to an aware UTC datetime (so all datetimes are timezone-aware)
before doing the min/max comparisons; update the handling of the local variable
created (and dt) so oldest and newest are compared against consistently
UTC-aware datetimes instead of mixing naive and aware datetimes.
🪄 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: 56056237-f6d0-4f44-a0d5-9435243e3e5e

📥 Commits

Reviewing files that changed from the base of the PR and between 68ad3e4 and d0f4a30.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/memory/storage/lancedb_storage.py
  • lib/crewai/tests/memory/test_unified_memory.py

Comment on lines +147 to +148
assert record.created_at == datetime(2026, 1, 1, 12, tzinfo=timezone.utc)
assert record.last_accessed == datetime(2026, 1, 1, 12, tzinfo=timezone.utc)
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 | 🟡 Minor | ⚡ Quick win

Assert tzinfo explicitly in normalization test.

Current equality checks can still pass when the value is offset-aware but not normalized to timezone.utc. Add direct tzinfo assertions to enforce the contract.

Suggested fix
     assert record.created_at == datetime(2026, 1, 1, 12, tzinfo=timezone.utc)
     assert record.last_accessed == datetime(2026, 1, 1, 12, tzinfo=timezone.utc)
+    assert record.created_at.tzinfo == timezone.utc
+    assert record.last_accessed.tzinfo == timezone.utc
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert record.created_at == datetime(2026, 1, 1, 12, tzinfo=timezone.utc)
assert record.last_accessed == datetime(2026, 1, 1, 12, tzinfo=timezone.utc)
assert record.created_at == datetime(2026, 1, 1, 12, tzinfo=timezone.utc)
assert record.last_accessed == datetime(2026, 1, 1, 12, tzinfo=timezone.utc)
assert record.created_at.tzinfo == timezone.utc
assert record.last_accessed.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/tests/memory/test_unified_memory.py` around lines 147 - 148, The
test currently asserts equality of record.created_at and record.last_accessed
but doesn't ensure their tzinfo is normalized to timezone.utc; update the
normalization test to explicitly assert that record.created_at.tzinfo is
timezone.utc and that record.last_accessed.tzinfo is timezone.utc (referencing
the variables record.created_at and record.last_accessed in the
test_unified_memory.py test) so the contract enforces UTC-aware datetimes.

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