Summary
The terminal panel is permanently stuck at "connecting" when using a self-hosted instance behind a Caddy reverse proxy with encode gzip enabled. The root cause is that Caddy's gzip middleware compresses the /api/control/stream SSE response, which causes the browser's EventSource API to drop the connection immediately.
Root cause
Caddy's encode gzip directive applies compression to all responses, including text/event-stream responses. This is incompatible with SSE:
- SSE requires unbuffered, real-time flushing of individual events
- gzip compression buffers output before sending, breaking the protocol
- The browser's
EventSource implementation receives a Content-Encoding: gzip header on what should be an uncompressed stream and drops the connection within ~1ms
Because the SSE stream never stays connected, the frontend never receives the workspace ready/status events, and the terminal WebSocket connection is never initiated — leaving the UI stuck at "connecting" indefinitely.
Impact
Any deployment using a reverse proxy with gzip compression (Caddy, nginx with gzip on, etc.) without explicit SSE exclusions will silently break the terminal. The rest of the UI (missions, agent output, etc.) continues to work because standard JSON responses are unaffected.
Suggested fix
The default Caddyfile template shipped with sandboxed.sh (or the documentation) should exclude streaming endpoints from compression:
sandboxed-sh-agent.example.com {
reverse_proxy localhost:3000
@compressible {
not path /api/control/stream /api/desktop/stream /api/task/*/stream /api/monitoring/ws/v1
not header Accept text/event-stream
}
encode @compressible gzip
request_body {
max_size 50MB
}
}
Alternatively, the server itself could set Cache-Control: no-transform on SSE responses, which instructs compliant proxies not to modify the response encoding.
Environment
- sandboxed_sh v0.11.1
- Caddy v2.x (reverse proxy)
- Chrome 144 (macOS)
Summary
The terminal panel is permanently stuck at "connecting" when using a self-hosted instance behind a Caddy reverse proxy with
encode gzipenabled. The root cause is that Caddy's gzip middleware compresses the/api/control/streamSSE response, which causes the browser'sEventSourceAPI to drop the connection immediately.Root cause
Caddy's
encode gzipdirective applies compression to all responses, includingtext/event-streamresponses. This is incompatible with SSE:EventSourceimplementation receives aContent-Encoding: gzipheader on what should be an uncompressed stream and drops the connection within ~1msBecause the SSE stream never stays connected, the frontend never receives the workspace ready/status events, and the terminal WebSocket connection is never initiated — leaving the UI stuck at "connecting" indefinitely.
Impact
Any deployment using a reverse proxy with gzip compression (Caddy, nginx with
gzip on, etc.) without explicit SSE exclusions will silently break the terminal. The rest of the UI (missions, agent output, etc.) continues to work because standard JSON responses are unaffected.Suggested fix
The default Caddyfile template shipped with sandboxed.sh (or the documentation) should exclude streaming endpoints from compression:
Alternatively, the server itself could set
Cache-Control: no-transformon SSE responses, which instructs compliant proxies not to modify the response encoding.Environment