From 60779ba1f8669234f174efab53c4c705ca2624c3 Mon Sep 17 00:00:00 2001 From: Godopu Date: Fri, 16 Oct 2026 04:05:13 +0000 Subject: [PATCH] fix(agents): a startedAt too large for a float counts as live (Issue #31, review) json.load reads a huge integer as an int, and math.isfinite raised OverflowError on it outside any try: conversation_in_use and purge_artifacts (which must never raise) threw, where 6f4f40a had taken it as live. The conversion to float now comes first, and an overflow means live. Co-Authored-By: Claude Opus 5.5 --- .agents/skills/lib_py/agents/adapters/claude.py | 13 +++++++++---- tests/test_issue31_purge_residual.py | 9 ++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.agents/skills/lib_py/agents/adapters/claude.py b/.agents/skills/lib_py/agents/adapters/claude.py index 50e092d..bf74770 100644 --- a/.agents/skills/lib_py/agents/adapters/claude.py +++ b/.agents/skills/lib_py/agents/adapters/claude.py @@ -78,12 +78,17 @@ def _session_file_live(data: dict, pid) -> bool: except OSError: pass # startedAt is epoch ms: a finite positive number. A bool, a string, NaN - # (every comparison with it is false) or an infinity is no time at all. + # (every comparison with it is false), an infinity or an int too large + # for a float is no time at all. started = data.get("startedAt") - if isinstance(started, bool) or not isinstance(started, (int, float)) \ - or not math.isfinite(started) or started <= 0: + if isinstance(started, bool) or not isinstance(started, (int, float)): + return True + try: + started = float(started) / 1000.0 + except OverflowError: + return True + if not math.isfinite(started) or started <= 0: return True - started = started / 1000.0 # lstart in UTC: a local time is ambiguous in the hour a DST change repeats. try: out = subprocess.run(["ps", "-p", str(pid), "-o", "lstart="], capture_output=True, text=True, diff --git a/tests/test_issue31_purge_residual.py b/tests/test_issue31_purge_residual.py index 5e48604..da964b0 100644 --- a/tests/test_issue31_purge_residual.py +++ b/tests/test_issue31_purge_residual.py @@ -472,7 +472,8 @@ def test_liveness_reads_lstart_as_utc(tmp_path, monkeypatch, live): @pytest.mark.parametrize("started", ["NaN", float("nan"), float("inf"), float("-inf"), "inf", "1760000000000", - "soon", True, False, None, [1], ""]) + "soon", True, False, None, [1], pytest.param(10 ** 400, id="10**400"), + ""]) def test_an_unusable_started_at_means_live(live, started): from lib_py.agents.adapters import claude as claude_mod data = {} if started == "" else {"startedAt": started} @@ -480,10 +481,12 @@ def test_an_unusable_started_at_means_live(live, started): assert claude_mod._session_file_live(data, _dead_pid()) is False, "a dead pid holds nothing" -@pytest.mark.parametrize("raw", ["NaN", '"NaN"', "Infinity", "true"]) +@pytest.mark.parametrize("raw", ["NaN", '"NaN"', "Infinity", "true", + pytest.param("1" + "0" * 400, id="10**400")]) def test_a_live_holder_with_an_unusable_started_at_blocks_the_purge(home, tmp_path, conv, live, raw): """json.load turns a bare NaN or Infinity into a float that every - comparison fails, which once made a live holder look stale.""" + comparison fails, which once made a live holder look stale, and a huge + integer into an int no float can hold.""" ws = tmp_path / "ws" ws.mkdir() sfile, key = _session_file(home / ".claude", live.pid, conv)