Skip to content

Commit 143ac4d

Browse files
fix(agentex): run SSE status recheck before the read so read failures can't bypass it
The recheck ran after the read cycle, so persistent read errors jumped straight to the retry/backoff handler and never reached it — a terminal task whose event publish was lost could stay open while Redis reads kept failing. Move the interval-gated status recheck to the top of the loop so it runs every iteration, including after a backoff, independent of read health. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1ce9552 commit 143ac4d

1 file changed

Lines changed: 23 additions & 21 deletions

File tree

agentex/src/domain/use_cases/streams_use_case.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,29 @@ async def stream_task_events(
133133
# Application-level control loop
134134
while True:
135135
try:
136+
# Authoritative status recheck on an interval. Runs at the
137+
# TOP of every iteration — even after a read failure/backoff —
138+
# so a terminal task ends even if its event publish was lost
139+
# or Redis reads keep erroring.
140+
current_time = asyncio.get_running_loop().time()
141+
if current_time - last_status_check >= ping_interval:
142+
last_status_check = current_time
143+
try:
144+
task = await self.task_service.get_task(id=task_id)
145+
except ItemDoesNotExist:
146+
# Row permanently gone (e.g. retention) — end, don't retry.
147+
logger.info(
148+
f"Ending SSE stream for task {task_id}: "
149+
"task no longer exists"
150+
)
151+
return
152+
if task.status in TERMINAL_TASK_STATUSES:
153+
logger.info(
154+
f"Ending SSE stream for task {task_id}: "
155+
"terminal on status recheck"
156+
)
157+
return
158+
136159
# Process yielded messages one by one
137160
message_generator = self.read_messages(
138161
topic=stream_topic, last_id=last_id
@@ -163,27 +186,6 @@ async def stream_task_events(
163186
# healthy again, so reset the backoff/error counter.
164187
consecutive_errors = 0
165188

166-
current_time = asyncio.get_running_loop().time()
167-
# Authoritative status recheck on an interval, busy or idle,
168-
# in case a terminal event's publish was lost.
169-
if current_time - last_status_check >= ping_interval:
170-
last_status_check = current_time
171-
try:
172-
task = await self.task_service.get_task(id=task_id)
173-
except ItemDoesNotExist:
174-
# Row permanently gone (e.g. retention) — end, don't retry.
175-
logger.info(
176-
f"Ending SSE stream for task {task_id}: "
177-
"task no longer exists"
178-
)
179-
return
180-
if task.status in TERMINAL_TASK_STATUSES:
181-
logger.info(
182-
f"Ending SSE stream for task {task_id}: "
183-
"terminal on status recheck"
184-
)
185-
return
186-
187189
# Idle: send keepalive ping so proxies don't reap us.
188190
if message_count == 0:
189191
if current_time - last_message_time >= ping_interval:

0 commit comments

Comments
 (0)