fix: handle bare {"stage":"name"} loading payload; harden watcher against parser errors

The stage-transition payload {"state":"loading","payload":{"stage":"mmproj_model"}}
lacks the stages/current/value keys — accessing them raised KeyError which crashed
the LogWatcher task, freezing the UI at the last good state indefinitely.

- log_parser: skip loading payloads missing stages/current/value (next message
  has the full payload within milliseconds)
- log_watcher: wrap on_line calls in try/except so any future parser error
  keeps the tail loop alive instead of killing the task

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vlad Doloman
2026-06-26 18:27:52 +03:00
parent e2996ff0b3
commit 7bda83998e
2 changed files with 12 additions and 2 deletions

View File

@@ -200,6 +200,10 @@ class LogParser:
payload = data.get("payload") payload = data.get("payload")
if s == "loading" and payload: if s == "loading" and payload:
# Some transition messages only carry {"stage":"name"} with no
# progress info — skip them; the next message has the full payload.
if not all(k in payload for k in ("stages", "current", "value")):
return None
loading = LoadingInfo( loading = LoadingInfo(
stages=payload["stages"], stages=payload["stages"],
current=payload["current"], current=payload["current"],

View File

@@ -56,7 +56,10 @@ class LogWatcher:
while True: while True:
line = f.readline() line = f.readline()
if line: if line:
try:
await self._on_line(line) await self._on_line(line)
except Exception:
pass # keep tailing even if a line causes a parser error
else: else:
await asyncio.sleep(self.POLL_INTERVAL) await asyncio.sleep(self.POLL_INTERVAL)
except OSError: except OSError:
@@ -96,7 +99,10 @@ class LogWatcher:
line = f.readline() line = f.readline()
if line: if line:
pos = f.tell() pos = f.tell()
try:
await self._on_line(line) await self._on_line(line)
except Exception:
pass # keep tailing even if a line causes a parser error
else: else:
break break