fix(web-tabs): bound time-to-headers, not the whole proxied exchange (#237, #238)

Reported by @DodgyBadger.

#237: the proxy wrapped each upstream fetch in a 30s `AbortSignal.timeout`, which
bounded the ENTIRE exchange rather than the wait for response headers. A dashboard
endpoint doing model inference, and any actively streaming response, both died at
30s as a generic 502 that Codeman never logged, so it read as an intermittent
network error. The timeout now bounds time-to-headers only and is cleared the
moment headers arrive, so a slow endpoint and a long stream both survive. The
default moves to 300s because "the app is thinking" is normal for the dashboards
people proxy; abandoned upstreams are reclaimed by the client-hangup abort rather
than by this value.

A browser that navigates away mid-request now aborts the upstream fetch, guarded
by `writableFinished` for the same reason as `abortOnClientHangUp` in
session-routes: `close` also fires after a completed response and must not abort
anything. Header timeouts are logged as a warning with a sanitized identity
(method plus origin plus path, never the query string, which can carry the
dashboard's tokens), and a client hangup is deliberately not warned since nobody
is listening and it would read as the dashboard being broken.

The WebSocket handshake keeps its own 30s budget
(`CODEMAN_WEBVIEW_WS_HANDSHAKE_TIMEOUT_MS`), decoupled from the request timeout:
a handshake is connection establishment, and waiting minutes on one only delays
the browser's reconnect logic.

#238: the web-tab guide covered sandboxed dashboards having no cookies, but not
cookie authentication in front of Codeman itself (Cloudflare Access and similar),
where a sandboxed frame's asset and API requests carry no auth cookie, bounce to
the login provider, and leave the embedded app looking unstyled or broken while
trusted mode works. Documented, and the Test button's result now says it probes
server-to-upstream reachability only, not how the page behaves in a sandboxed
frame.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-09 04:06:23 +02:00
parent 94aa53c65b
commit 6e89eb9ec1
6 changed files with 252 additions and 8 deletions
+4 -1
View File
@@ -708,7 +708,10 @@
<span class="form-hint">
Recommended. A proxied dashboard is served from Codeman's own address, so unchecking
this lets its JavaScript read this page and call the API that starts agents. Uncheck
only for a dashboard you fully trust, or one whose own login needs cookies.
only for a dashboard you fully trust, or one whose own login needs cookies. Also
uncheck it if Codeman itself sits behind a cookie-authenticated reverse proxy
(e.g. Cloudflare Access): a sandboxed frame carries no auth cookie, so its asset
and API requests bounce to the login provider and the page loads broken.
</span>
</div>
<div class="form-row">
+6 -2
View File
@@ -396,9 +396,13 @@ Object.assign(CodemanApp.prototype, {
out.textContent = 'Test failed (invalid URL?).';
return;
}
// #238: the probe runs server-to-upstream; say so, or a passing Test reads as
// "the embedded page will work" when the browser sandbox / a cookie-auth
// reverse proxy in front of Codeman can still break it.
out.textContent = probe.reachable
? `Reachable (HTTP ${probe.status}). ${probe.reason}`
: `Not reachable. ${probe.reason}`;
? `Reachable (HTTP ${probe.status}) from the Codeman server. ${probe.reason} ` +
`(Tests server-to-upstream reachability only, not how the page behaves in a sandboxed frame.)`
: `Not reachable from the Codeman server. ${probe.reason}`;
out.className = 'form-hint webview-probe-result ' + (probe.reachable ? 'ok' : 'bad');
},