fix(result): normalize result artifact timestamps#928
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a8baaead0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if value.tzinfo is None or value.tzinfo.utcoffset(value) is None: | ||
| value = value.replace(tzinfo=UTC) |
There was a problem hiding this comment.
Convert naive local times before writing UTC artifacts
When BenchFlow runs on a host whose local timezone is not UTC, native rollouts still create started_at with _init_rollout()'s local naive datetime.now() (src/benchflow/rollout/_setup.py:272). This branch then simply attaches UTC to that wall time, so result.json/config.json claim e.g. 10:00 local is 10:00Z instead of converting to the actual UTC instant, corrupting artifact ordering and cross-run comparisons outside UTC environments.
Useful? React with 👍 / 👎.
| assert "total_tokens" not in data | ||
|
|
||
| def test_result_timestamps_are_timezone_aware_iso8601(self, tmp_path): | ||
| """Guards issue #528: result.json timestamps are ISO 8601 UTC strings.""" |
There was a problem hiding this comment.
Name the guarded commit in timestamp regression tests
For these new regression tests, the docstrings cite only issue #528; /workspace/benchflow/AGENTS.md requires regression tests to name the PR or commit they guard. Please include this commit hash or the eventual PR number here and in the similar timestamp regression docstring below so future maintainers can trace the intended regression boundary.
Useful? React with 👍 / 👎.
|
Daily scan follow-up (2026-07-13): pushed What changed:
Local validation after the dependency update:
Waiting on refreshed GitHub checks for head |
|
CI follow-up (2026-07-13): refreshed GitHub checks are now green on head Passed checks:
Labels moved back to |
|
Users Simulation automation review (2026-07-13): ready by simulation on head Scope checked: result/config timestamp normalization, Evidence:
Artifact health:
Thermo-nuclear review: no blocker. The timestamp helper is small and canonical, the compatibility mirrors now read from |
Summary
Closes #528.
This keeps
agent_resultas the canonical result metrics object while preserving the existing top-leveln_tool_calls,n_skill_invocations, andn_promptsfields as compatibility mirrors. Both native rollout and hosted-env writers now source those mirrors fromagent_result, so the old flat fields cannot drift independently.It also normalizes artifact timestamps at the result/config serialization boundary:
result.json.started_atandresult.json.finished_atnow serialize as timezone-aware UTC ISO 8601 strings with aZsuffix.config.json.started_atand hosted-envconfig.json.started_atuse the same formatter.started_atinputs are handled without naive/aware datetime arithmetic failures.CI security follow-up
Fresh CI hit two transitive
pip-auditadvisories after the first push:click 8.3.1->8.4.2cryptography 46.0.7->48.0.1cryptography>=48.0.1requires moving the exactlitellm[proxy]pin from1.89.0to1.91.0, the first stable release in this range whose proxy extra permits the fixed cryptography line. LiteLLM-focused tests and the full suite pass on the updated lock.Why
Issue #528 called out two related artifact-shape problems:
agent_resultmetrics;str(datetime), producing space-separated values with no timezone while other artifact fields already used ISO 8601.Removing the top-level metric mirrors in one shot would break existing contract tests and likely downstream consumers, so this PR makes
agent_resultthe source of truth first and leaves removal of compatibility mirrors as a deliberate later schema break.Validation
uv sync --extra dev --extra sandbox-daytona --lockeduv run --extra dev python -m pytest tests/test_verify.py::TestResultJson tests/test_hosted_env_rollout_contract.py::test_hosted_env_writes_contract_result_json -q-> 7 passeduv run --extra dev python -m pytest tests/test_verify.py tests/test_hosted_env_rollout_contract.py tests/test_sdk_internals.py::TestBuildResult tests/test_config_redaction.py tests/test_usage_litellm.py tests/test_native_acp_usage.py tests/test_metrics.py -q-> 133 passeduv run --extra dev python -m pytest tests/test_litellm_config.py tests/test_litellm_hardening.py tests/test_litellm_logging.py tests/test_litellm_smoke.py tests/test_usage_litellm.py tests/test_agent_registry.py -q-> 134 passed, 2 skippeduv run --extra dev python -m pytest tests/test_trace_import_cli.py::test_tasks_generate_rejects_removed_short_options -q-> 4 passeduv run --extra dev python -m pytest tests/ -q-> 4855 passed, 16 skipped, 7 deselecteduv export --locked --extra dev --extra sandbox-daytona --extra sandbox-modal --format requirements-txt --no-hashes --output-file /tmp/req-audit && uv run --with pip-audit pip-audit -r /tmp/req-audit --no-deps --disable-pip-> no known vulnerabilities founduv run ruff format --check pyproject.toml src/benchflow/_utils/timestamps.py src/benchflow/rollout/_results.py src/benchflow/hosted_env.py tests/test_verify.py tests/test_hosted_env_rollout_contract.py tests/test_trace_import_cli.pyuv run ruff check .uv run ty check src/