Remote stack logs: surface proxy/agent errors instead of silent disconnect (0.13.1)

Remote-stack log streaming showed only "disconnected, 0 lines" whenever the
agent log proxy failed, because the LogViewer ignored type:"error" messages and
the proxy swallowed connection errors.

- ws.py: the agent-logs proxy now reports a clear, logged reason on failure —
  distinguishes "cannot reach agent <url>" from a handshake rejection (HTTP 404
  hints the agent is outdated and lacks live-log support) and forwards abnormal
  upstream close codes (e.g. 4401 bad agent token).
- LogViewer: renders type:"error" messages (red) and surfaces a 4401 close as an
  authorization error, instead of silently showing "Waiting for log output…".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
menzelj
2026-06-08 11:14:58 +00:00
co-authored by Claude Opus 4.8
parent 25bba1cf2c
commit 5ebd615651
5 changed files with 61 additions and 12 deletions
+19 -2
View File
@@ -25,11 +25,14 @@ export function LogViewer({ stackId, agentId }: { stackId: string; agentId?: num
const [lines, setLines] = useState<{ service: string | null; line: string }[]>([]);
const [autoScroll, setAutoScroll] = useState(true);
const [connected, setConnected] = useState(false);
const [error, setError] = useState<string | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
const token = useAuthStore((s) => s.accessToken);
useEffect(() => {
if (!token) return;
setError(null);
let gotError = false;
const proto = window.location.protocol === "https:" ? "wss" : "ws";
const path =
agentId != null
@@ -38,7 +41,13 @@ export function LogViewer({ stackId, agentId }: { stackId: string; agentId?: num
const url = `${proto}://${window.location.host}${path}?token=${token}`;
const ws = new WebSocket(url);
ws.onopen = () => setConnected(true);
ws.onclose = () => setConnected(false);
ws.onclose = (ev) => {
setConnected(false);
// Auth rejection from the proxy/agent (JWT or agent token) closes 4401.
if (!gotError && ev.code === 4401) {
setError("Not authorized to stream logs (session or agent token).");
}
};
ws.onmessage = (ev) => {
try {
const msg = JSON.parse(ev.data);
@@ -47,6 +56,9 @@ export function LogViewer({ stackId, agentId }: { stackId: string; agentId?: num
const next = [...prev, { service: msg.service, line: msg.line }];
return next.length > MAX_LINES ? next.slice(-MAX_LINES) : next;
});
} else if (msg.type === "error") {
gotError = true;
setError(msg.detail || "Log stream error");
}
} catch {
/* ignore */
@@ -96,7 +108,12 @@ export function LogViewer({ stackId, agentId }: { stackId: string; agentId?: num
ref={containerRef}
className="flex-1 overflow-auto rounded-lg bg-slate-950 p-3 font-mono text-xs leading-relaxed"
>
{lines.length === 0 && (
{error && (
<div className="mb-1 whitespace-pre-wrap break-words text-rose-400">
{error}
</div>
)}
{lines.length === 0 && !error && (
<div className="text-slate-500">Waiting for log output</div>
)}
{lines.map((l, i) => (