Files
coder/site
Andrew AquinoandDanielle Maywood 197c814070 feat: correlate web terminal sessions by client_session_id (#27677)
## What

Implements the web terminal client half of the
[Connection log collection and correlation
RFC](https://www.notion.so/coderhq/Connection-log-collection-and-correlation-36ed579be5928025a56cd11fe58661fb)
(`DEVEX-663`). Generates a per-session correlation ID and attaches it to
the
web terminal's requests and client logs so a single session can be
traced end
to end.

> Stacked on #27671 (`DEVEX-659`, the coderd tracing middleware that
reads the
> `client_session_id` baggage). Review/merge that first.

## Changes

**Session ID**
- 16-byte value encoded as a 32-character hex string, per RFC
requirement 1.
Generated with the `generateConnectionSessionId` function added in
#27935
- Minted once per web terminal session: on `TerminalPage` load and on
`AgentsPage` terminal panel mount. Unlike the reconnection token, it is
**not**
persisted in the URL, so a reload (or a new tab) is a new session,
matching the
  RFC's session definition.

**Propagation**
- **HTTP API request:** the reconnecting-pty signed-token request
carries the ID
via W3C baggage (`baggage: client_session_id=<hex>`), which the
DEVEX-659 middleware
  reads.
- **PTY WebSocket:** browsers cannot set the `baggage` header on a
WebSocket
handshake (the codebase already works around this for the session
token), so
the ID is sent as a `client_session_id` query parameter instead. The
reconnecting-pty
WebSocket handler reads and validates it and attaches it to the request
and PTY
  logs.

**Client logs**
- The terminal's connection-error `console.error` logs now include
`client_session_id`.

**Telemetry:** the web terminal emits none today, so there is nothing to
tag
(confirmed with the issue reporter).

## Testing

- `site`: unit tests for `generateSessionId` (format + uniqueness) and
`terminalWebsocketUrl` (query param). Updated `TerminalPage.test.tsx`
(mocks the
generator to a fixed ID and asserts the WebSocket URL includes
`client_session_id`).
  `tsc`, Biome, and the React Compiler check pass.
- `coderd`: `go test ./coderd/tracing/...` and `go vet
./coderd/workspaceapps/...`
  pass; new `ValidSessionID` export reused by the PTY handler.

<details>
<summary>Design notes / decision log</summary>

- **Session vs reconnection token.** The existing `reconnect` token is
deliberately persisted in the URL to survive reloads.
`client_session_id` is the
opposite: a fresh value per page load, matching the RFC (a reload is a
new
  session). They are separate identifiers.
- **Per-request baggage, not a global axios default.** The frontend
axios
instance is a singleton shared by the whole app; a global `baggage`
default
would tag unrelated requests. The header is attached only to the
terminal's
  signed-token request.
- **WebSocket uses a query param.** Browser `WebSocket` cannot send
custom
headers, so baggage is impossible on the PTY handshake. The
`client_session_id` query
  parameter is the counterpart, read server-side in `workspaceAgentPTY`.
- **Server-side scope.** Reading the query param is localized to the web
terminal's own WebSocket handler rather than broadening the shared
tracing
  middleware to trust query params on every route.
- **Validation.** Both the baggage and query-param paths validate the
value as a
32-char hex string (`tracing.ValidSessionID`) before logging, to avoid
logging
  arbitrary client-controlled input.
- **AgentsPage compiler constraint.** `AgentsPage` is React Compiler
optimized
(no `useMemo`/`useCallback`), so the panel mints its ID with `useState`
lazy
  init instead.
- **Out of scope (other RFC tickets):** agent-side middleware,
`connection_logs`
columns, Tailnet state-change logging, and the CLI
`CODER_TRACE_SESSION_ID`
  env var.

</details>

---

_Opened by Coder Agents on behalf of @aqandrew._

---------

Co-authored-by: Danielle Maywood <danielle@themaywoods.com>
2026-08-27 12:55:33 -07:00
..
…
…
…
…