fix: normalize rollout timestamps before deriving started_at/ended_at#556
fix: normalize rollout timestamps before deriving started_at/ended_at#556eric-tramel wants to merge 1 commit intomainfrom
Conversation
Claude Code, Codex, and ATIF ingesters called min()/max() on raw timestamp strings. That assumes lexicographic order matches chronological order, which breaks for ISO 8601 timestamps with mixed UTC offsets or precisions (e.g. 2025-01-01T00:30:00+01:00 is earlier than 2025-01-01T00:00:00Z but sorts later as a string). Introduce min_max_timestamps() in the shared rollout utils that parses each value as ISO 8601 (naive values treated as UTC, unparseable values skipped) and picks the chronological extremes, returning them in their original string form. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Eric W. Tramel <eric.tramel@gmail.com>
Code Review: PR #556 — fix: normalize rollout timestamps before deriving started_at/ended_atSummaryThis PR fixes a correctness bug in the agent rollout ingesters (Claude Code, Codex, ATIF) where The fix introduces two helpers in Files changed: 5 (3 ingesters, 1 utility module, 1 new test file) FindingsCorrectness
Design
Style & Conventions
Tests
Potential Concerns (non-blocking)
VerdictApprove. This is a clean, well-scoped bug fix with good test coverage. The implementation is correct, follows project conventions, and doesn't introduce unnecessary complexity. No blocking issues found. |
Greptile SummaryThis PR replaces raw string
|
| Filename | Overview |
|---|---|
| packages/data-designer-engine/src/data_designer/engine/resources/agent_rollout/utils.py | Adds parse_iso8601() and min_max_timestamps() helpers; Z→+00:00 normalisation is correct, naive timestamps are coerced to UTC, unparseable values return None. |
| packages/data-designer-engine/src/data_designer/engine/resources/agent_rollout/atif.py | Replaces min/max string comparisons with min_max_timestamps(); change is minimal and correct. |
| packages/data-designer-engine/src/data_designer/engine/resources/agent_rollout/claude_code.py | Replaces min/max string comparisons with min_max_timestamps(); change is minimal and correct. |
| packages/data-designer-engine/src/data_designer/engine/resources/agent_rollout/codex.py | Replaces min/max string comparisons with min_max_timestamps(); session_meta["timestamp"] short-circuit for started_at is correctly preserved. |
| packages/data-designer-engine/tests/engine/resources/agent_rollout/test_utils.py | New parameterised test covering empty list, mixed UTC offsets, mixed precision, naive-vs-aware, unparseable-skip, and all-unparseable cases — all correct. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["timestamps list[str]"] --> B{for each timestamp}
B --> C["parse_iso8601(value)"]
C --> D{ValueError?}
D -- Yes --> E["skip - return None"]
D -- No --> F{tzinfo present?}
F -- No --> G["add UTC tzinfo"]
F -- Yes --> H["keep as-is"]
G --> I["append datetime + original string"]
H --> I
I --> B
B --> J{list empty?}
J -- Yes --> K["return None, None"]
J -- No --> L["min by datetime - earliest string"]
J -- No --> M["max by datetime - latest string"]
L --> N["return earliest, latest"]
M --> N
N --> O["started_at / ended_at in NormalizedAgentRolloutRecord"]
Reviews (1): Last reviewed commit: "fix: normalize rollout timestamps before..." | Re-trigger Greptile
looks like this file has the same |
📋 Summary
Parse ISO 8601 timestamps before comparing them in the Claude Code, Codex, and ATIF rollout ingesters. Raw string
min()/max()can order chronologically-earlier timestamps as later when offsets or precisions differ (e.g.2025-01-01T00:30:00+01:00vs2025-01-01T00:00:00Z), which could produce incorrectstarted_at/ended_aton otherwise valid rollouts.🔗 Related Issue
Fixes #497
🔄 Changes
min_max_timestamps()+parse_iso8601()helpers inagent_rollout/utils.py. Naive timestamps are treated as UTC; unparseable values are silently skipped (matching the existing tolerance ofcoerce_optional_str). Winners are returned in their original string form, so the output contract is unchanged.min(timestamps)/max(timestamps)for the helper inclaude_code.py,codex.py, andatif.py. Codex'ssession_meta["timestamp"]short-circuit forstarted_atis preserved.🧪 Testing
make testpasses (engine suite, 1867 tests; pre-existing interface failures unrelated to this change)✅ Checklist