Problem
The AgentAPI chat UI is completely broken in corporate environments that use SSL inspection proxies (e.g., Netskope, Zscaler, Palo Alto). These proxies terminate long-lived SSE connections over HTTP/2, causing ERR_HTTP2_PROTOCOL_ERROR in the browser.
This affects both subdomain and path-based routing — the proxy kills the /events SSE stream regardless of the URL path. Regular short-lived requests (/status, /messages) work fine.
Evidence
Performance entries from the browser show the issue clearly:
| Endpoint |
Protocol |
Duration |
Result |
/status |
h2 |
~50ms |
✅ Works |
/messages |
h2 |
~50ms |
✅ Works |
/events |
"" (failed) |
16+ seconds |
❌ ERR_HTTP2_PROTOCOL_ERROR |
The EventSource never receives any events — readyState goes from 0 (CONNECTING) straight to 2 (CLOSED), and the onerror handler fires immediately. Since chat-provider.tsx relies entirely on SSE for both message updates (message_update events) and status changes (status_change events), the chat input stays permanently disabled and no messages are ever rendered.
Environment
- Coder v2.30.4
- AgentAPI via
coder-labs/codex module (registry v1.2.0)
- Corporate SSL inspection proxy: Netskope (
ca.<tenant>.goskope.com)
- Tested with both
subdomain = true and subdomain = false — same result
Proposed Solution
Add a polling fallback in chat/src/components/chat-provider.tsx that uses the existing REST endpoints (GET /messages and GET /status) when the SSE connection fails.
Approach
The simplest implementation would be to detect SSE failure and automatically fall back to polling:
// In the useEffect that sets up the EventSource:
// 1. Try SSE first (current behavior)
const eventSource = new EventSource(`${agentAPIUrl}/events`);
// 2. If SSE fails, fall back to polling
eventSource.onerror = () => {
eventSource.close();
console.warn("SSE connection failed, falling back to polling");
const poll = async () => {
try {
const [messagesRes, statusRes] = await Promise.all([
fetch(`${agentAPIUrl}/messages`),
fetch(`${agentAPIUrl}/status`),
]);
const messagesData = await messagesRes.json();
const statusData = await statusRes.json();
setMessages(messagesData.messages.map((m) => ({
id: m.id, role: m.role, content: m.content,
})));
setServerStatus(statusData.status);
setAgentType(statusData.agent_type || "unknown");
} catch {
setServerStatus("offline");
}
};
poll();
pollIntervalRef.current = setInterval(poll, 1000);
};
Trade-offs
- Lost:
agent_error events have no REST equivalent — these would not be available in polling mode
- Reduced responsiveness: SSE pushes updates in ~25ms; polling at 1s intervals adds latency
- Slightly higher server load: periodic HTTP requests vs. a single long-lived connection
Alternative: configurable option
An environment variable like NEXT_PUBLIC_TRANSPORT=polling|sse|auto could also work, where auto (default) tries SSE first and falls back to polling. This would give administrators explicit control in environments where SSE is known to be broken.
Impact
This issue makes AgentAPI chat completely unusable in corporate environments with SSL inspection proxies — a common setup in enterprise deployments. The chat input is permanently disabled, and no conversation history is shown, even though the AgentAPI backend is fully functional.
Problem
The AgentAPI chat UI is completely broken in corporate environments that use SSL inspection proxies (e.g., Netskope, Zscaler, Palo Alto). These proxies terminate long-lived SSE connections over HTTP/2, causing
ERR_HTTP2_PROTOCOL_ERRORin the browser.This affects both subdomain and path-based routing — the proxy kills the
/eventsSSE stream regardless of the URL path. Regular short-lived requests (/status,/messages) work fine.Evidence
Performance entries from the browser show the issue clearly:
/statush2/messagesh2/events""(failed)ERR_HTTP2_PROTOCOL_ERRORThe EventSource never receives any events —
readyStategoes from0(CONNECTING) straight to2(CLOSED), and theonerrorhandler fires immediately. Sincechat-provider.tsxrelies entirely on SSE for both message updates (message_updateevents) and status changes (status_changeevents), the chat input stays permanently disabled and no messages are ever rendered.Environment
coder-labs/codexmodule (registry v1.2.0)ca.<tenant>.goskope.com)subdomain = trueandsubdomain = false— same resultProposed Solution
Add a polling fallback in
chat/src/components/chat-provider.tsxthat uses the existing REST endpoints (GET /messagesandGET /status) when the SSE connection fails.Approach
The simplest implementation would be to detect SSE failure and automatically fall back to polling:
Trade-offs
agent_errorevents have no REST equivalent — these would not be available in polling modeAlternative: configurable option
An environment variable like
NEXT_PUBLIC_TRANSPORT=polling|sse|autocould also work, whereauto(default) tries SSE first and falls back to polling. This would give administrators explicit control in environments where SSE is known to be broken.Impact
This issue makes AgentAPI chat completely unusable in corporate environments with SSL inspection proxies — a common setup in enterprise deployments. The chat input is permanently disabled, and no conversation history is shown, even though the AgentAPI backend is fully functional.