fix(tui): show log timestamps on the viewer's local clock - #55
Merged
Conversation
Backend timestamps are UTC ISO strings and the TUI rendered them verbatim, so a 17:24 local event showed as 15:24 (CEST). The session list was worse: it explicitly re-normalised created_at to UTC before formatting. Convert once, in one place: render.format_local() parses the ISO value, treats an offset-naive value as UTC (never shifting a clock twice), and formats in the process-local zone. Both the live message feed and the agent error/warning rows go through it, and the session list now reuses it with the date format instead of its own datetime handling. Backend-side log lines and stream text that print an absolute time keep their explicit "UTC" label — the backend has no knowledge of the viewer's timezone.
The retry line published to the live feed spelled out "next attempt at 15:26 UTC" — the only absolute clock time left in the TUI after moving the timestamp columns to local time, and rendered right next to a local-clock timestamp. It cannot be converted server-side either: the backend runs in a container whose zone is UTC regardless of the host, so astimezone() there would print the same digits with the label removed. Keep the relative wait, which needs no timezone at all. The absolute instant is still on the event's next_attempt_at field if the TUI ever wants to show it in local time.
akozak-gd
approved these changes
Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TUI timestamps were rendered as raw UTC wall-clock time, so an event that happened at 17:24 local (CEST) showed up as
15:24:…in the message feed. The session list was worse — it explicitly called.astimezone(timezone.utc)oncreated_atbefore formatting, pinning the list to UTC no matter where the user sits.Every timestamp the TUI renders now shows on the viewer's local clock, and no UTC clock time reaches the TUI at all.
Entrypoint
mcp_server/tui/render.py—format_local()replaces the private_hhmmss()helper and is the single place the TUI turns a backend ISO timestamp into a displayable clock time.Details
render.format_local(timestamp, fmt=LOCAL_TIME_FORMAT)parses the ISO value, treats an offset-naive value as UTC (so a clock time is never shifted twice), then formats in the process-local zone. Returns""for missing/unparseable input, as before.stream_row) and agent error/warning rows (agent_error_event_rows) both go through it — same call sites as the old helper, now local._session_labelinapp.py) reuses the same function withLOCAL_DATE_TIME_FORMATinstead of its ownfromisoformat().astimezone(timezone.utc).strftime(); the raw-prefix fallback for unparseable values is kept.datetime/timezoneimports dropped fromapp.py.claude_code.py) no longer spells outnext attempt at 15:26 UTC. It was the last absolute clock time reaching the TUI, sitting right next to a local-clock timestamp. Converting it server-side is not an option: the backend runs in a container whose zone is UTC regardless of the host, soastimezone()there prints the same digits with the label removed. The relative wait (waiting 2m 0s before retry (crash 2/6)) needs no timezone, and the absolute instant remains on the event's structurednext_attempt_atfield if the TUI ever wants to render it locally.UTClabel — they are not TUI surfaces (theRecent activitylog tail that would show them is currently disabled inbuild_dashboard).Tests
New
mcp_server/tests/conftest.pyexposes alocal_timezonefixture (context-manager factory pinningTZ+time.tzset()) so local-time assertions are deterministic instead of machine-dependent.TestFormatLocal: UTC → local conversion, offset-naive read as UTC, non-UTC offset input, day rollover in the date format, blank/unparseable input.14:02:31Z→16:02:31inEurope/Warsaw)._session_label: created_at shown in local time, plus the unparseable fallback.make unit-tests→ 702 MCP-server tests + full backend suite pass.