Skip to content

fix(logstore): normalize workflow timestamps to naive UTC - #40947

Merged
asukaminato0721 merged 2 commits into
langgenius:mainfrom
amogh-nagri-11:fix/logstore-naive-utc-timestamps
Aug 20, 2026
Merged

fix(logstore): normalize workflow timestamps to naive UTC#40947
asukaminato0721 merged 2 commits into
langgenius:mainfrom
amogh-nagri-11:fix/logstore-naive-utc-timestamps

Conversation

@amogh-nagri-11

Copy link
Copy Markdown
Contributor

Summary

Fixes #40943.

The two LogStore repositories deserialize workflow timestamps with calls that resolve against the host's local clock, while the rest of the codebase stores naive UTC:

  • datetime.fromtimestamp(ts) without tz → naive local time
  • datetime.now() → naive local time
  • datetime.fromisoformat(s)aware when the string carries an offset, naive otherwise

On its own that would just be a uniform offset. The real problem is that created_at and finished_at are matched independently, so one record's two timestamps can be produced by different branches and land in different frames — and _dict_to_workflow_run then subtracts them for elapsed_time.

Observed on a UTC+05:30 host (reproduction in the issue drives the real functions):

Payload Before After
both epoch timestamps shifted +5:30 correct naive UTC
aware ISO + epoch TypeError: can't subtract offset-naive and offset-aware datetimes elapsed_time = 30.0
naive ISO + epoch 30-second run reported as 19830 s elapsed_time = 30.0
started_at missing elapsed_time negative (-49875.0) defaults to naive_utc_now()

On a UTC host all four coincidentally agree, which is why this is easy to miss.

Changes

Every branch of both match statements now terminates in naive UTC, using the helpers that already exist in libs/datetime_utils:

  • datetime.now()naive_utc_now() (the same call the Postgres write path uses)
  • datetime.fromtimestamp(ts)datetime.fromtimestamp(ts, tz=UTC).replace(tzinfo=None)
  • datetime.fromisoformat(s) → wrapped in ensure_naive_utc(...), which normalizes aware values and is a no-op for naive ones
  • the already-a-datetime passthrough branch is wrapped in ensure_naive_utc too, so an aware value from upstream can't reintroduce the mixed-frame subtraction

Applied to all six call sites across logstore_api_workflow_run_repository.py and logstore_api_workflow_node_execution_repository.py. One change fixes the offset shift, the TypeError, and both bad elapsed_time results together.

Scope note: this path is only reachable when the Aliyun SLS LogStore backend is enabled, so deployments on the default Postgres path are unaffected. No behavior change on UTC hosts.

Tests

Added regression coverage that pins TZ=Asia/Kolkata via time.tzset(), so the assertions are meaningful on the UTC CI host rather than passing by coincidence. It covers all four payload shapes plus the missing-timestamp default, for both repositories.

All 10 new tests fail on main and pass with this change:

uv run --project api --dev pytest api/tests/unit_tests/extensions/logstore/repositories/
13 passed

ruff check, ruff format --check, mypy and pyrefly are clean on the changed files (the two pre-existing bad-instantiation pyrefly diagnostics in the node-execution test file are on untouched lines).

Screenshots

Not applicable — backend-only timezone correctness fix.

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.
  • I ran make lint && make type-check (backend) and vp staged (frontend) to appease the lint gods

The LogStore repositories converted timestamps with `datetime.fromtimestamp(ts)`
and `datetime.now()`, both of which return naive *local* time, while the rest of
the codebase stores naive UTC. `datetime.fromisoformat` also returns an aware
value when the string carries an offset.

Because `created_at` and `finished_at` are matched independently, one record's
two timestamps could come from different branches and end up in different
frames, so the `elapsed_time` subtraction produced garbage. On a UTC+05:30 host
a 30-second run was reported as 19830 seconds, a missing `started_at` yielded a
negative `elapsed_time`, and an offset-bearing ISO string raised
`TypeError: can't subtract offset-naive and offset-aware datetimes`.

Route every branch of both `match` statements through `ensure_naive_utc` /
`naive_utc_now` and pass `tz=UTC` to `fromtimestamp`, so all values land in
naive UTC regardless of the host timezone.
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Aug 18, 2026
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Pyrefly Diff

base → PR
--- /tmp/pyrefly_base.txt	2026-08-19 03:30:39.634723894 +0000
+++ /tmp/pyrefly_pr.txt	2026-08-19 03:30:26.357618702 +0000
@@ -7975,9 +7975,9 @@
 ERROR Object of class `Events` has no attribute `request_start` [missing-attribute]
   --> tests/unit_tests/events/test_events_package_compat.py:33:5
 ERROR Cannot instantiate `LogstoreAPIWorkflowNodeExecutionRepository` because the following members are abstract: `get_execution_snapshots_by_workflow_run`, `delete_expired_executions`, `count_by_runs`, `delete_by_runs`, `delete_executions_by_app`, `get_expired_executions_batch`, `delete_executions_by_ids`, `get_offloads_by_execution_ids`, `save`, `save_synchronously`, `save_execution_data`, `get_by_workflow_execution` [bad-instantiation]
-  --> tests/unit_tests/extensions/logstore/repositories/test_logstore_api_workflow_node_execution_repository.py:12:64
+  --> tests/unit_tests/extensions/logstore/repositories/test_logstore_api_workflow_node_execution_repository.py:18:64
 ERROR Cannot instantiate `LogstoreAPIWorkflowNodeExecutionRepository` because the following members are abstract: `get_execution_snapshots_by_workflow_run`, `delete_expired_executions`, `count_by_runs`, `delete_by_runs`, `delete_executions_by_app`, `get_expired_executions_batch`, `delete_executions_by_ids`, `get_offloads_by_execution_ids`, `save`, `save_synchronously`, `save_execution_data`, `get_by_workflow_execution` [bad-instantiation]
-  --> tests/unit_tests/extensions/logstore/repositories/test_logstore_api_workflow_node_execution_repository.py:21:64
+  --> tests/unit_tests/extensions/logstore/repositories/test_logstore_api_workflow_node_execution_repository.py:27:64
 ERROR Cannot index into `object` [bad-index]
    --> tests/unit_tests/extensions/otel/test_celery_sqlcommenter.py:136:20
 ERROR Cannot index into `object` [bad-index]

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Pyrefly Type Coverage

Metric Base PR Delta
Type coverage 59.69% 59.70% +0.01%
Strict coverage 59.28% 59.28% +0.01%
Typed symbols 39,917 39,929 +12
Untyped symbols 27,142 27,142 0
Modules 3181 3182 +1

@asukaminato0721
asukaminato0721 added this pull request to the merge queue Aug 20, 2026
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Aug 20, 2026
Merged via the queue into langgenius:main with commit 54ffbc7 Aug 20, 2026
86 of 92 checks passed
@amogh-nagri-11

Copy link
Copy Markdown
Contributor Author

Thank you @asukaminato0721 for the quick review and merge!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LogStore repositories convert workflow timestamps to local time, producing shifted timestamps, negative elapsed_time, and a TypeError

3 participants