Skip to content

fix(tui): show log timestamps on the viewer's local clock - #55

Merged
akozak-gd merged 3 commits into
mainfrom
claude/tui-logs-local-timestamps-43408e
Jul 29, 2026
Merged

fix(tui): show log timestamps on the viewer's local clock#55
akozak-gd merged 3 commits into
mainfrom
claude/tui-logs-local-timestamps-43408e

Conversation

@mkonopelski-gd

@mkonopelski-gd mkonopelski-gd commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

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) on created_at before 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.pyformat_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.
  • Live message feed (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 list (_session_label in app.py) reuses the same function with LOCAL_DATE_TIME_FORMAT instead of its own fromisoformat().astimezone(timezone.utc).strftime(); the raw-prefix fallback for unparseable values is kept. datetime/timezone imports dropped from app.py.
  • Retry stream line (claude_code.py) no longer spells out next 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, so astimezone() 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 structured next_attempt_at field if the TUI ever wants to render it locally.
  • Backend server logs keep their explicit UTC label — they are not TUI surfaces (the Recent activity log tail that would show them is currently disabled in build_dashboard).

Tests

New mcp_server/tests/conftest.py exposes a local_timezone fixture (context-manager factory pinning TZ + 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.
  • Existing stream-row and error-row time assertions updated to the local-clock contract (14:02:31Z16:02:31 in Europe/Warsaw).
  • _session_label: created_at shown in local time, plus the unparseable fallback.

make unit-tests → 702 MCP-server tests + full backend suite pass.

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
akozak-gd merged commit 07d8b89 into main Jul 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants