Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions python/packages/kagent-adk/src/kagent/adk/_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,15 @@ async def _read_kagent_token(self) -> str | None:
async def _refresh_token(self):
while True:
await asyncio.sleep(60) # Wait for 60 seconds before refreshing
token = await self._read_kagent_token()
if token is not None and token != self.token:
async with self.update_lock:
self.token = token
try:
token = await self._read_kagent_token()
if token is not None and token != self.token:
async with self.update_lock:
self.token = token
except Exception:
# A single failed read must not kill the loop, or the token
# would never refresh again for the life of the process.
logger.exception("Error refreshing kagent token, will retry next cycle")

async def _add_headers(self, request: httpx.Request):
token = await self._get_token()
Expand Down
43 changes: 43 additions & 0 deletions python/packages/kagent-adk/tests/unittests/test_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Tests for KAgentTokenService's background refresh loop."""

import asyncio
from unittest.mock import patch

import pytest

from kagent.adk._token import KAgentTokenService


@pytest.mark.asyncio
async def test_refresh_token_survives_unexpected_error():
"""One failing read must not permanently stop the refresh loop.

Regression test: _refresh_token only guarded against a token read
returning None; any other exception from the read used to propagate out
of the while loop and kill the background task for the rest of the
process's life.
"""
service = KAgentTokenService(app_name="test-agent")
service.token = "old-token"

read_calls = 0

async def fake_sleep(_seconds):
return None

async def fake_read():
nonlocal read_calls
read_calls += 1
if read_calls == 1:
raise ValueError("unexpected decode error")
if read_calls == 2:
return "new-token"
# Stop the loop once the point is proven.
raise asyncio.CancelledError

with patch("asyncio.sleep", fake_sleep), patch.object(service, "_read_kagent_token", fake_read):
with pytest.raises(asyncio.CancelledError):
await service._refresh_token()

assert read_calls == 3, "the loop must keep running after the first read raises"
assert service.token == "new-token", "a later successful read must still update the token"
Loading