fix(agents): compare a claude process's lstart in UTC (Issue #31, Mac review)

time.mktime read ps's local lstart, which is ambiguous in the hour a DST
change repeats: the start could come out an hour off and a live holder be
taken for a reused pid (or the reverse). ps now runs with TZ=UTC0, which BSD
ps and procps both honour, and calendar.timegm reads it. No /proc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-16 03:23:17 +00:00
1 parent f943b0db30
commit af8ed397f6
2 files changed
+28 -3

No files matched your search

+24
View File
@@ -447,6 +447,30 @@ def test_session_file_liveness_fails_safe(monkeypatch, live):
assert claude_mod._session_file_live({"startedAt": hour_ago}, live.pid) is True, "ps unavailable: live"
def test_liveness_reads_lstart_as_utc(tmp_path, monkeypatch, live):
"""2026-11-01 01:30 happens twice in EST5EDT. A process that started at
the second one (06:30 UTC) shows lstart 01:30 to a local-time ps; read as
the first (05:30 UTC) it predates a session file written at 05:45. ps
runs with TZ=UTC0 instead, which BSD ps and procps both honour."""
import calendar
from lib_py.agents.adapters import claude as claude_mod
fake = tmp_path / "fakebin"
fake.mkdir()
(fake / "ps").write_text('#!/bin/sh\nif [ "$TZ" = UTC0 ]; then echo "Sun Nov 1 06:30:00 2026"; '
'else echo "Sun Nov 1 01:30:00 2026"; fi\n')
(fake / "ps").chmod(0o755)
utc = lambda hhmmss: calendar.timegm(time.strptime(f"2026-11-01 {hhmmss}", "%Y-%m-%d %H:%M:%S")) * 1000 # noqa: E731
try:
with monkeypatch.context() as m:
m.setenv("PATH", f"{fake}:{os.environ['PATH']}")
m.setenv("TZ", "EST5EDT,M3.2.0,M11.1.0")
time.tzset()
assert claude_mod._session_file_live({"startedAt": utc("05:45:00")}, live.pid) is False, "reused pid"
assert claude_mod._session_file_live({"startedAt": utc("06:30:02")}, live.pid) is True
finally:
time.tzset()
@pytest.mark.parametrize("started", ["NaN", float("nan"), float("inf"), float("-inf"), "inf", "1760000000000",
"soon", True, False, None, [1], "<missing>"])
def test_an_unusable_started_at_means_live(live, started):