← Back to Kontasks
SSE Recovery Bug Analysis
Root cause analysis and fix proposal for console disconnect recovery
Output
🔍 SSE Recovery Bug - Root Cause Found
🚨 The Problem
When you navigate away or close the browser:
- SSE stream disconnects
executeStream()catches the error- Calls
pollForOutput()(line 2241) - BUT: Polls fail silently because page is hidden
- When you return,
state.isRunningmay already befalse page_visiblecheck fails:s.isRunning && s.currentRequestId- Recovery never triggers!
🔑 The Root Cause
The state machine's page_visible transition only triggers if s.isRunning is true. But the recovery should ALSO check localStorage pending state, not just memory state. The pending state in localStorage survives page navigation - the memory state doesn't.
✅ Proposed Fix
Two-part fix:
// 1. page_visible should check localStorage, not just state case 'page_visible': { // Check localStorage for pending request const pending = loadPendingState(); if (isPendingStateValid(pending.timestamp)) { return { state: { ...s, connectionState: 'polling', isRunning: true, currentRequestId: pending.requestId, recoveryInProgress: true }, effects: [{ type: 'start_polling' }] }; } return { state: s, effects: [] }; }
// 2. attemptRecovery should be called by effect, not separately case 'start_polling': // Restore state from pending before polling const pending = loadPendingState(); if (pending.requestId) { state.currentRequestId = pending.requestId; } pollForOutput(); break;
📊 Why This Will Work
| localStorage persists | Survives page navigation and browser close |
| requestId stored | Matches kontask to request |
| 5 min validity | Won't recover stale requests |
| State machine effect | Recovery is deterministic, not ad-hoc |
What's Next?
Ready to implement this fix. It's a targeted change to the state machine's page_visible transition and the start_polling effect. Should I proceed?
Quick Actions
Original Request
No layout configured
Details
Type
General
Status
Completed
Scope
vibetools
Tags
consolesserecoverybug
Created
2 Jan 2026, 4:55 pm
Updated
2 Jan 2026, 4:55 pm
Created By
claude
Raw Data
{
"id": "0537bc42-b2cc-4751-93d6-2a3357d8854b",
"type": "general",
"status": "completed",
"title": "SSE Recovery Bug Analysis",
"description": "Root cause analysis and fix proposal for console disconnect recovery",
"context": {
"output": "<div style=\"background:#1e1e1e;color:#e0e0e0;padding:20px;border-radius:8px;font-family:sans-serif;\"><h2 style=\"color:#ef4444;margin:0 0 16px 0;\">🔍 SSE Recovery Bug - Root Cause Found</h2><div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:16px;\"><h4 style=\"margin:0 0 12px 0;color:#fff;\">🚨 The Problem</h4><p style=\"color:#e0e0e0;margin:0 0 12px 0;\">When you navigate away or close the browser:</p><ol style=\"margin:0;padding-left:20px;color:#9ca3af;line-height:1.8;\"><li>SSE stream disconnects</li><li><code>executeStream()</code> catches the error</li><li>Calls <code>pollForOutput()</code> (line 2241)</li><li><strong>BUT: Polls fail silently because page is hidden</strong></li><li>When you return, <code>state.isRunning</code> may already be <code>false</code></li><li><code>page_visible</code> check fails: <code>s.isRunning && s.currentRequestId</code></li><li>Recovery never triggers!</li></ol></div><div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:16px;\"><h4 style=\"margin:0 0 12px 0;color:#f59e0b;\">🔑 The Root Cause</h4><p style=\"color:#e0e0e0;margin:0;\">The state machine's <code>page_visible</code> transition only triggers if <code>s.isRunning</code> is true. But the recovery should ALSO check <strong>localStorage pending state</strong>, not just memory state. The pending state in localStorage survives page navigation - the memory state doesn't.</p></div><div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:16px;\"><h4 style=\"margin:0 0 12px 0;color:#22c55e;\">✅ Proposed Fix</h4><p style=\"color:#9ca3af;margin:0 0 8px 0;\">Two-part fix:</p><div style=\"background:#1a1a1a;padding:12px;border-radius:4px;font-family:monospace;font-size:0.85rem;margin:8px 0;\"><pre style=\"margin:0;color:#e0e0e0;\"><span style=\"color:#7f848e;\">// 1. page_visible should check localStorage, not just state</span>\n<span style=\"color:#c678dd;\">case</span> <span style=\"color:#98c379;\">'page_visible'</span>: {\n <span style=\"color:#7f848e;\">// Check localStorage for pending request</span>\n <span style=\"color:#c678dd;\">const</span> pending = loadPendingState();\n <span style=\"color:#c678dd;\">if</span> (isPendingStateValid(pending.timestamp)) {\n <span style=\"color:#c678dd;\">return</span> {\n state: { \n ...s, \n connectionState: <span style=\"color:#98c379;\">'polling'</span>,\n isRunning: <span style=\"color:#d19a66;\">true</span>,\n currentRequestId: pending.requestId,\n recoveryInProgress: <span style=\"color:#d19a66;\">true</span>\n },\n effects: [{ type: <span style=\"color:#98c379;\">'start_polling'</span> }]\n };\n }\n <span style=\"color:#c678dd;\">return</span> { state: s, effects: [] };\n}</pre></div><div style=\"background:#1a1a1a;padding:12px;border-radius:4px;font-family:monospace;font-size:0.85rem;margin:8px 0;\"><pre style=\"margin:0;color:#e0e0e0;\"><span style=\"color:#7f848e;\">// 2. attemptRecovery should be called by effect, not separately</span>\n<span style=\"color:#c678dd;\">case</span> <span style=\"color:#98c379;\">'start_polling'</span>:\n <span style=\"color:#7f848e;\">// Restore state from pending before polling</span>\n <span style=\"color:#c678dd;\">const</span> pending = loadPendingState();\n <span style=\"color:#c678dd;\">if</span> (pending.requestId) {\n state.currentRequestId = pending.requestId;\n }\n pollForOutput();\n <span style=\"color:#c678dd;\">break</span>;</pre></div></div><div style=\"background:#2d2d2d;padding:16px;border-radius:6px;margin-bottom:16px;\"><h4 style=\"margin:0 0 12px 0;color:#61afef;\">📊 Why This Will Work</h4><table style=\"width:100%;border-collapse:collapse;font-size:0.85rem;\"><tr style=\"border-bottom:1px solid #3d3d5c;\"><td style=\"padding:6px;color:#9ca3af;\">localStorage persists</td><td style=\"padding:6px;\">Survives page navigation and browser close</td></tr><tr style=\"border-bottom:1px solid #3d3d5c;\"><td style=\"padding:6px;color:#9ca3af;\">requestId stored</td><td style=\"padding:6px;\">Matches kontask to request</td></tr><tr style=\"border-bottom:1px solid #3d3d5c;\"><td style=\"padding:6px;color:#9ca3af;\">5 min validity</td><td style=\"padding:6px;\">Won't recover stale requests</td></tr><tr><td style=\"padding:6px;color:#9ca3af;\">State machine effect</td><td style=\"padding:6px;\">Recovery is deterministic, not ad-hoc</td></tr></table></div><div style=\"background:#2d2d2d;padding:16px;border-radius:6px;\"><h4 style=\"margin:0 0 12px 0;color:#fff;\">What's Next?</h4><p style=\"color:#9ca3af;margin:0;\">Ready to implement this fix. It's a targeted change to the state machine's <code>page_visible</code> transition and the <code>start_polling</code> effect. Should I proceed?</p></div></div>",
"requestedAt": "2026-01-02T13:15:00Z",
"requestId": "a0fab377-ff47-4758-9593-49ef39f4108d",
"choices": [
{
"label": "Implement the fix",
"value": "implement-fix",
"primary": true
},
{
"label": "Need more details",
"value": "more-details"
},
{
"label": "Different approach",
"value": "different-approach"
}
]
},
"createdBy": "claude",
"createdAt": "2026-01-02T06:55:27.284Z",
"updatedAt": "2026-01-02T06:55:27.415Z",
"requestId": "a0fab377-ff47-4758-9593-49ef39f4108d",
"scope": "vibetools",
"tags": [
"console",
"sse",
"recovery",
"bug"
],
"targetUser": "claude"
}