From c9b1131cd8d35c408273f4dff5011c1749633c25 Mon Sep 17 00:00:00 2001 From: "kindralai[bot]" Date: Fri, 10 Jul 2026 13:07:31 +0000 Subject: [PATCH 1/2] fix: log timestamps now respect configured timezone (#289) _BufferHandler.emit() used datetime.fromtimestamp() without a tz argument, which defaulted to the system (Docker) UTC regardless of the user's configured timezone in agent config. Changes: - admin.py: add _LOG_TIMEZONE module variable (default 'UTC') and set_log_timezone() to update it after config loads - admin.py: emit() now passes tz=ZoneInfo(_LOG_TIMEZONE) to datetime.fromtimestamp() - main.py: call set_log_timezone(config.agent.timezone) after config is loaded in _start_agent() - tests: two new tests verifying UTC default and US/Eastern offset Closes #289. --- humux/api/admin.py | 12 ++++++++- humux/core/main.py | 5 +++- humux/tests/test_log_streams.py | 45 +++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/humux/api/admin.py b/humux/api/admin.py index 0b106652..9028e4fa 100644 --- a/humux/api/admin.py +++ b/humux/api/admin.py @@ -423,6 +423,16 @@ def __init__(self, agent: AgentCore | None = None, status: str = "STOPPED"): # Severity levels offered in the Logs tab filter, low → high. _LOG_LEVELS = ["DEBUG", "INFO", "WARNING", "ERROR"] +# Timezone for log timestamps (updated after config loads, defaults to UTC). +_LOG_TIMEZONE: str = "UTC" + + +def set_log_timezone(tz_name: str | None) -> None: + """Update the log timezone (called after config loads, #289).""" + global _LOG_TIMEZONE + if tz_name: + _LOG_TIMEZONE = tz_name + def _should_capture_log_record(record: logging.LogRecord) -> bool: """Return True when a record should be visible in the admin log viewer.""" @@ -453,7 +463,7 @@ def emit(self, record: logging.LogRecord) -> None: _LOG_BUFFER.append( { "ts": record.created, - "time": datetime.fromtimestamp(record.created).strftime("%H:%M:%S"), + "time": datetime.fromtimestamp(record.created, tz=ZoneInfo(_LOG_TIMEZONE)).strftime("%H:%M:%S"), "level": record.levelname, "levelno": record.levelno, "name": record.name, diff --git a/humux/core/main.py b/humux/core/main.py index 4e506daf..81a55104 100644 --- a/humux/core/main.py +++ b/humux/core/main.py @@ -23,7 +23,7 @@ from fastapi import Depends, Request from fastapi.responses import HTMLResponse -from api.admin import AgentState, create_admin_app, install_log_buffer +from api.admin import AgentState, create_admin_app, install_log_buffer, set_log_timezone from core.config_store import ConfigStore from core.email_config import materialize_himalaya_config from core.secret_store import SecretStore @@ -54,6 +54,9 @@ async def _start_agent(config_store: ConfigStore): await _secret_store.load_infra_cache() config = await config_store.export_to_config(vault_resolve=_secret_store.infra_resolve) + # Apply the configured timezone to log timestamps (#289) + set_log_timezone(config.agent.timezone) + agent = AgentCore(config, secret_store=_secret_store) # Ensure scheduler jobs can resolve the current agent instance diff --git a/humux/tests/test_log_streams.py b/humux/tests/test_log_streams.py index df940d56..fa41b56b 100644 --- a/humux/tests/test_log_streams.py +++ b/humux/tests/test_log_streams.py @@ -101,3 +101,48 @@ def test_bad_regex_is_noop_not_crash() -> None: def test_stream_hue_is_stable() -> None: assert _stream_hue("default") == _stream_hue("default") assert 0 <= _stream_hue("coding-helper") < 360 + + +def test_log_timestamp_defaults_to_utc() -> None: + """Without a timezone configured, timestamps are formatted in UTC.""" + import api.admin as admin + + saved = admin._LOG_TIMEZONE + admin._LOG_TIMEZONE = "UTC" + try: + handler = _BufferHandler() + rec = logging.LogRecord("core.agent", logging.INFO, __file__, 1, "hi", None, None) + rec.created = 1700006400.0 # 2023-11-15 00:00:00 UTC + captured: list[dict] = [] + saved_buf = admin._LOG_BUFFER + admin._LOG_BUFFER = captured + try: + handler.emit(rec) + finally: + admin._LOG_BUFFER = saved_buf + assert captured[0]["time"] == "00:00:00" + finally: + admin._LOG_TIMEZONE = saved + + +def test_log_timestamp_respects_configured_timezone() -> None: + """Log timestamps are formatted in the configured timezone.""" + import api.admin as admin + + saved = admin._LOG_TIMEZONE + admin._LOG_TIMEZONE = "US/Eastern" + try: + handler = _BufferHandler() + rec = logging.LogRecord("core.agent", logging.INFO, __file__, 1, "hi", None, None) + rec.created = 1700006400.0 # 2023-11-15 00:00:00 UTC + captured: list[dict] = [] + saved_buf = admin._LOG_BUFFER + admin._LOG_BUFFER = captured + try: + handler.emit(rec) + finally: + admin._LOG_BUFFER = saved_buf + # US/Eastern in Nov is EST (UTC-5): 2023-11-14 19:00:00 + assert captured[0]["time"] == "19:00:00" + finally: + admin._LOG_TIMEZONE = saved From 01edabfb755c0fdb776c9af294fde9fef22a9151 Mon Sep 17 00:00:00 2001 From: "kindralai[bot]" Date: Fri, 10 Jul 2026 13:13:25 +0000 Subject: [PATCH 2/2] fix: split long line to pass ruff E501 (line too long) lint check datetime.fromtimestamp() call exceeded 100-char limit, causing CI lint failure on PR #290. Broke it across multiple lines. --- humux/api/admin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/humux/api/admin.py b/humux/api/admin.py index 9028e4fa..bc3f84c9 100644 --- a/humux/api/admin.py +++ b/humux/api/admin.py @@ -463,7 +463,9 @@ def emit(self, record: logging.LogRecord) -> None: _LOG_BUFFER.append( { "ts": record.created, - "time": datetime.fromtimestamp(record.created, tz=ZoneInfo(_LOG_TIMEZONE)).strftime("%H:%M:%S"), + "time": datetime.fromtimestamp( + record.created, tz=ZoneInfo(_LOG_TIMEZONE) + ).strftime("%H:%M:%S"), "level": record.levelname, "levelno": record.levelno, "name": record.name,