From 770ff04975c3b8137cf9ef5e07fda3fde57d023f Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Sat, 28 Feb 2026 13:23:01 +0100 Subject: [PATCH 1/2] fix: Clamp negative timedelta in `_get_remaining_time()` When `timeout_at` is already in the past, the subtraction produces a negative timedelta that would be passed to API calls via `start`, `call`, or `call_task` with `timeout='inherit'`. Clamp to zero instead. Co-Authored-By: Claude Opus 4.6 --- src/apify/_actor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apify/_actor.py b/src/apify/_actor.py index dc0fe844..6b556cf3 100644 --- a/src/apify/_actor.py +++ b/src/apify/_actor.py @@ -1398,7 +1398,7 @@ def _get_default_exit_process(self) -> bool: def _get_remaining_time(self) -> timedelta | None: """Get time remaining from the Actor timeout. Returns `None` if not on an Apify platform.""" if self.is_at_home() and self.configuration.timeout_at: - return self.configuration.timeout_at - datetime.now(tz=timezone.utc) + return max(self.configuration.timeout_at - datetime.now(tz=timezone.utc), timedelta(0)) self.log.warning( 'Using `inherit` or `RemainingTime` argument is only possible when the Actor' From 13710670d2bafd8128828407a947e43ffb878fbb Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Mon, 2 Mar 2026 09:34:31 +0100 Subject: [PATCH 2/2] test: Add unit tests for _get_remaining_time() clamping Co-Authored-By: Claude Opus 4.6 --- tests/unit/actor/test_actor_helpers.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/unit/actor/test_actor_helpers.py b/tests/unit/actor/test_actor_helpers.py index fe77a9c9..5b15ce82 100644 --- a/tests/unit/actor/test_actor_helpers.py +++ b/tests/unit/actor/test_actor_helpers.py @@ -2,7 +2,7 @@ import asyncio import warnings -from datetime import timedelta +from datetime import datetime, timedelta, timezone from typing import TYPE_CHECKING import pytest @@ -321,3 +321,26 @@ async def test_get_remaining_time_warns_when_not_at_home(caplog: pytest.LogCaptu result = Actor._get_remaining_time() assert result is None assert any('inherit' in msg or 'RemainingTime' in msg for msg in caplog.messages) + + +async def test_get_remaining_time_clamps_negative_to_zero() -> None: + """Test that _get_remaining_time returns timedelta(0) instead of a negative value when timeout is in the past.""" + async with Actor: + Actor.configuration.is_at_home = True + Actor.configuration.timeout_at = datetime.now(tz=timezone.utc) - timedelta(minutes=5) + + result = Actor._get_remaining_time() + assert result is not None + assert result == timedelta(0) + + +async def test_get_remaining_time_returns_positive_when_timeout_in_future() -> None: + """Test that _get_remaining_time returns a positive timedelta when timeout is in the future.""" + async with Actor: + Actor.configuration.is_at_home = True + Actor.configuration.timeout_at = datetime.now(tz=timezone.utc) + timedelta(minutes=5) + + result = Actor._get_remaining_time() + assert result is not None + assert result > timedelta(0) + assert result <= timedelta(minutes=5)