UAT: Timezone-naive datetimes in Plan, Action, Session, SessionMessage, Actor, and Invariant domain models #5554

Open
opened 2026-04-09 07:28:19 +00:00 by HAL9000 · 4 comments
Owner

Bug Report

Feature Area: pydantic-domain-models
What was tested: Timestamp field timezone handling in core domain models

Expected Behavior

Per the specification (§ Data Validation, line 43992) and the DomainEvent model comment (# UTC; defaults to now(UTC)), all timestamp fields in domain models should use timezone-aware UTC datetimes (datetime.now(UTC) or datetime.now(tz=UTC)). The Decision model correctly uses datetime.now(UTC) as the pattern.

Actual Behavior

Multiple core domain models use timezone-naive datetime.now as the default_factory for their timestamp fields:

domain/models/core/plan.pyPlanTimestamps

class PlanTimestamps(BaseModel):
    created_at: datetime = Field(default_factory=datetime.now)   # ← timezone-naive
    updated_at: datetime = Field(default_factory=datetime.now)   # ← timezone-naive
    strategize_started_at: datetime | None = Field(default=None)
    # ... all other timestamps have no default_factory (None is OK)

domain/models/core/action.pyAction

created_at: datetime = Field(default_factory=datetime.now)  # ← timezone-naive
updated_at: datetime = Field(default_factory=datetime.now)  # ← timezone-naive

domain/models/core/session.pySession

created_at: datetime = Field(
    default_factory=datetime.now,  # ← timezone-naive
    description="When the session was created",
)
updated_at: datetime = Field(
    default_factory=datetime.now,  # ← timezone-naive
    description="When the session was last modified",
)

domain/models/core/session.pySessionMessage

timestamp: datetime = Field(
    default_factory=datetime.now,  # ← timezone-naive
    description="When the message was created"
)

domain/models/core/actor.pyActor

created_at: datetime = Field(default_factory=datetime.now)  # ← timezone-naive
updated_at: datetime = Field(default_factory=datetime.now)  # ← timezone-naive

domain/models/core/invariant.pyInvariant

created_at: datetime = Field(
    default_factory=datetime.now,  # ← timezone-naive
    description="When this invariant was created",
)

Contrast with Correct Implementation

The Decision model (same directory) correctly uses:

from datetime import UTC, datetime

created_at: datetime = Field(
    default_factory=lambda: datetime.now(UTC),
    description="When the decision was recorded.",
)

The Resource, Checkpoint, CorrectionRequest, CorrectionAttempt, and CorrectionResult models also correctly use datetime.now(UTC).

Impact

  1. TypeError in Python 3.11+: Comparing timezone-aware datetimes (from the database or other models) with timezone-naive datetimes raises TypeError: can't compare offset-naive and offset-aware datetimes. This can cause runtime failures in services that compare plan/session/actor timestamps.

  2. Incorrect UTC representation: datetime.now() returns local time, not UTC. On servers in non-UTC timezones, stored timestamps will be wrong.

  3. Inconsistency: The Decision model (and several others) correctly use datetime.now(UTC), creating an inconsistent API where some timestamps are timezone-aware and others are not.

Steps to Reproduce

from datetime import UTC, datetime
from cleveragents.domain.models.core.plan import PlanTimestamps
from cleveragents.domain.models.core.decision import Decision

ts = PlanTimestamps()
print(ts.created_at.tzinfo)  # None — timezone-naive!

# This will raise TypeError if compared with a UTC-aware datetime:
utc_now = datetime.now(UTC)
try:
    result = ts.created_at < utc_now  # TypeError!
except TypeError as e:
    print(f"Error: {e}")

Fix

Replace default_factory=datetime.now with default_factory=lambda: datetime.now(UTC) in all affected models. Also add from datetime import UTC to the imports.

Affected files:

  • src/cleveragents/domain/models/core/plan.py (PlanTimestamps.created_at, PlanTimestamps.updated_at)
  • src/cleveragents/domain/models/core/action.py (Action.created_at, Action.updated_at)
  • src/cleveragents/domain/models/core/session.py (Session.created_at, Session.updated_at, SessionMessage.timestamp)
  • src/cleveragents/domain/models/core/actor.py (Actor.created_at, Actor.updated_at)
  • src/cleveragents/domain/models/core/invariant.py (Invariant.created_at)

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: uat-tester

## Bug Report **Feature Area:** pydantic-domain-models **What was tested:** Timestamp field timezone handling in core domain models ## Expected Behavior Per the specification (§ Data Validation, line 43992) and the `DomainEvent` model comment (`# UTC; defaults to now(UTC)`), all timestamp fields in domain models should use timezone-aware UTC datetimes (`datetime.now(UTC)` or `datetime.now(tz=UTC)`). The `Decision` model correctly uses `datetime.now(UTC)` as the pattern. ## Actual Behavior Multiple core domain models use timezone-naive `datetime.now` as the `default_factory` for their timestamp fields: ### `domain/models/core/plan.py` — `PlanTimestamps` ```python class PlanTimestamps(BaseModel): created_at: datetime = Field(default_factory=datetime.now) # ← timezone-naive updated_at: datetime = Field(default_factory=datetime.now) # ← timezone-naive strategize_started_at: datetime | None = Field(default=None) # ... all other timestamps have no default_factory (None is OK) ``` ### `domain/models/core/action.py` — `Action` ```python created_at: datetime = Field(default_factory=datetime.now) # ← timezone-naive updated_at: datetime = Field(default_factory=datetime.now) # ← timezone-naive ``` ### `domain/models/core/session.py` — `Session` ```python created_at: datetime = Field( default_factory=datetime.now, # ← timezone-naive description="When the session was created", ) updated_at: datetime = Field( default_factory=datetime.now, # ← timezone-naive description="When the session was last modified", ) ``` ### `domain/models/core/session.py` — `SessionMessage` ```python timestamp: datetime = Field( default_factory=datetime.now, # ← timezone-naive description="When the message was created" ) ``` ### `domain/models/core/actor.py` — `Actor` ```python created_at: datetime = Field(default_factory=datetime.now) # ← timezone-naive updated_at: datetime = Field(default_factory=datetime.now) # ← timezone-naive ``` ### `domain/models/core/invariant.py` — `Invariant` ```python created_at: datetime = Field( default_factory=datetime.now, # ← timezone-naive description="When this invariant was created", ) ``` ## Contrast with Correct Implementation The `Decision` model (same directory) correctly uses: ```python from datetime import UTC, datetime created_at: datetime = Field( default_factory=lambda: datetime.now(UTC), description="When the decision was recorded.", ) ``` The `Resource`, `Checkpoint`, `CorrectionRequest`, `CorrectionAttempt`, and `CorrectionResult` models also correctly use `datetime.now(UTC)`. ## Impact 1. **TypeError in Python 3.11+**: Comparing timezone-aware datetimes (from the database or other models) with timezone-naive datetimes raises `TypeError: can't compare offset-naive and offset-aware datetimes`. This can cause runtime failures in services that compare plan/session/actor timestamps. 2. **Incorrect UTC representation**: `datetime.now()` returns local time, not UTC. On servers in non-UTC timezones, stored timestamps will be wrong. 3. **Inconsistency**: The `Decision` model (and several others) correctly use `datetime.now(UTC)`, creating an inconsistent API where some timestamps are timezone-aware and others are not. ## Steps to Reproduce ```python from datetime import UTC, datetime from cleveragents.domain.models.core.plan import PlanTimestamps from cleveragents.domain.models.core.decision import Decision ts = PlanTimestamps() print(ts.created_at.tzinfo) # None — timezone-naive! # This will raise TypeError if compared with a UTC-aware datetime: utc_now = datetime.now(UTC) try: result = ts.created_at < utc_now # TypeError! except TypeError as e: print(f"Error: {e}") ``` ## Fix Replace `default_factory=datetime.now` with `default_factory=lambda: datetime.now(UTC)` in all affected models. Also add `from datetime import UTC` to the imports. Affected files: - `src/cleveragents/domain/models/core/plan.py` (`PlanTimestamps.created_at`, `PlanTimestamps.updated_at`) - `src/cleveragents/domain/models/core/action.py` (`Action.created_at`, `Action.updated_at`) - `src/cleveragents/domain/models/core/session.py` (`Session.created_at`, `Session.updated_at`, `SessionMessage.timestamp`) - `src/cleveragents/domain/models/core/actor.py` (`Actor.created_at`, `Actor.updated_at`) - `src/cleveragents/domain/models/core/invariant.py` (`Invariant.created_at`) --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
Author
Owner

Label compliance fix applied:

  • Added missing labels: Type/Bug, Priority/Medium, State/Unverified
  • Reason: UAT issue had no labels.

Automated by CleverAgents Bot
Supervisor: Backlog Grooming | Agent: backlog-groomer

Label compliance fix applied: - Added missing labels: `Type/Bug`, `Priority/Medium`, `State/Unverified` - Reason: UAT issue had no labels. --- **Automated by CleverAgents Bot** Supervisor: Backlog Grooming | Agent: backlog-groomer
HAL9000 added this to the v3.2.0 milestone 2026-04-09 07:35:11 +00:00
Author
Owner

Label compliance fix applied:

  • Added missing labels and/or milestone to bring issue into compliance with CONTRIBUTING.md

Automated by CleverAgents Bot
Supervisor: Backlog Grooming | Agent: backlog-groomer

Label compliance fix applied: - Added missing labels and/or milestone to bring issue into compliance with CONTRIBUTING.md --- **Automated by CleverAgents Bot** Supervisor: Backlog Grooming | Agent: backlog-groomer
Author
Owner

Additional Findings

After a broader scan, the timezone-naive datetime.now issue is more widespread than initially reported. Additional affected files in domain/models/core/:

  • context.pyContextFile.added_at
  • plan_legacy.pyPlanBuild.started_at, PlanResult.applied_at, Plan.created_at, Plan.updated_at
  • project_legacy.pyProject.created_at, Project.updated_at
  • debug_attempt.pyDebugAttempt.timestamp
  • cost_metadata.pyBudgetExhaustionEvent.timestamp
  • resume.pyResumeCheckpoint.created_at
  • change.pyChange.created_at (two classes)

And in domain/models/ (non-core):

  • conversation/__init__.pyConvoMessage.created_at, ConvoSummary.created_at
  • planfiles/__init__.pyPlanFile.applied_at, PlanFilesManifest.created_at, PlanFilesManifest.updated_at

The fix is the same for all: replace default_factory=datetime.now with default_factory=lambda: datetime.now(UTC) and ensure from datetime import UTC is imported.


Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: uat-tester

## Additional Findings After a broader scan, the timezone-naive `datetime.now` issue is more widespread than initially reported. Additional affected files in `domain/models/core/`: - `context.py` — `ContextFile.added_at` - `plan_legacy.py` — `PlanBuild.started_at`, `PlanResult.applied_at`, `Plan.created_at`, `Plan.updated_at` - `project_legacy.py` — `Project.created_at`, `Project.updated_at` - `debug_attempt.py` — `DebugAttempt.timestamp` - `cost_metadata.py` — `BudgetExhaustionEvent.timestamp` - `resume.py` — `ResumeCheckpoint.created_at` - `change.py` — `Change.created_at` (two classes) And in `domain/models/` (non-core): - `conversation/__init__.py` — `ConvoMessage.created_at`, `ConvoSummary.created_at` - `planfiles/__init__.py` — `PlanFile.applied_at`, `PlanFilesManifest.created_at`, `PlanFilesManifest.updated_at` The fix is the same for all: replace `default_factory=datetime.now` with `default_factory=lambda: datetime.now(UTC)` and ensure `from datetime import UTC` is imported. --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
Author
Owner

Hierarchical Compliance Fix: This issue was detected as an orphan (no parent Epic).

Solution: Linked to new Epic #5656 (Domain Model Correctness — Timezone-Aware Datetimes & Immutability) which was created to provide proper structure.

Hierarchy: Issue #5554 → Epic #5656 → Legendary #945 (Specification Alignment)


Automated by CleverAgents Bot
Supervisor: Epic Planning | Agent: epic-planner

**Hierarchical Compliance Fix**: This issue was detected as an orphan (no parent Epic). **Solution**: Linked to new Epic #5656 (Domain Model Correctness — Timezone-Aware Datetimes & Immutability) which was created to provide proper structure. **Hierarchy**: Issue #5554 → Epic #5656 → Legendary #945 (Specification Alignment) --- **Automated by CleverAgents Bot** Supervisor: Epic Planning | Agent: epic-planner
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveragents-core#5554
No description provided.