fix(health): preserve transient failure codes through stale snapshot fallback

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-09-12 13:37:45 +00:00
committed by GitHub
co-authored by genichin
parent 4a2e7dd893
commit 9c5a01ed67
3 changed files with 25 additions and 4 deletions
@@ -209,3 +209,15 @@ def test_slots_rpc_normalizes_snapshot_store_read_failure():
with pytest.raises(AccountSnapshotUnavailable) as raised: with pytest.raises(AccountSnapshotUnavailable) as raised:
service.holdings_for_slots_rpc() service.holdings_for_slots_rpc()
assert raised.value.code == "read_failed" assert raised.value.code == "read_failed"
def test_sell_stale_snapshot_preserves_transient_live_failure_detail_code():
broker, _, service, now = _wire()
assert service.refresh()
broker.balance_error = "timeout"
now[0] += timedelta(seconds=3601)
with pytest.raises(AccountSnapshotUnavailable) as raised:
service.holdings_for_sell()
assert raised.value.code == "expired"
assert raised.value.detail_code == "timeout"
+8 -1
View File
@@ -214,6 +214,7 @@ class AccountSnapshotService:
def holdings_for_sell(self) -> list[BalanceItem]: def holdings_for_sell(self) -> list[BalanceItem]:
"""Prefer a live balance; fallback only on transient transport failure.""" """Prefer a live balance; fallback only on transient transport failure."""
transient_code: str | None = None
try: try:
return self._broker.get_balance() return self._broker.get_balance()
except BrokerBalanceError as exc: except BrokerBalanceError as exc:
@@ -221,8 +222,14 @@ class AccountSnapshotService:
raise AccountSnapshotUnavailable( raise AccountSnapshotUnavailable(
"live_read_failed", detail_code=exc.code "live_read_failed", detail_code=exc.code
) from None ) from None
transient_code = exc.code
snapshot = self._load_current() snapshot = self._load_current()
age = self._age(snapshot.holdings_captured_at, self._sell_max_age) try:
age = self._age(snapshot.holdings_captured_at, self._sell_max_age)
except AccountSnapshotUnavailable as exc:
raise AccountSnapshotUnavailable(
exc.code, detail_code=transient_code
) from None
_log.warning( _log.warning(
"sell balance using account snapshot: age_sec=%.0f", "sell balance using account snapshot: age_sec=%.0f",
age, age,
+5 -3
View File
@@ -443,9 +443,11 @@ class ExecutionService:
try: try:
holdings = self._sell_dispatch_balance_reader() holdings = self._sell_dispatch_balance_reader()
except AccountSnapshotUnavailable as exc: except AccountSnapshotUnavailable as exc:
code = "stale_snapshot" if exc.code == "expired" else ( detail_code = getattr(exc, "detail_code", None)
getattr(exc, "detail_code", None) or exc.code if exc.code == "expired" and detail_code is None:
) code = "stale_snapshot"
else:
code = detail_code or exc.code
lifecycle, backoff = self._balance_health.record_failure( lifecycle, backoff = self._balance_health.record_failure(
self._balance_worker, code=code self._balance_worker, code=code
) )