Skip to content

feat(analytics): threads and turns now know which client started them - #7774

Open
t3dotgg wants to merge 1 commit into
mainfrom
t3code/client-origin-analytics
Open

feat(analytics): threads and turns now know which client started them#7774
t3dotgg wants to merge 1 commit into
mainfrom
t3code/client-origin-analytics

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Aug 21, 2026

Copy link
Copy Markdown
Member

We can't answer basic product questions like "how many people use mobile" or "are threads started on desktop or phone": the server never learns which client sent a request. Pairing metadata lumps web and desktop together as "desktop", nothing carries an app version, and events, turns, and PostHog all have zero client attribution.

Now every client announces itself with two optional query params on the /ws upgrade, next to the existing wsTicket: clientSurface (web / desktop / mobile) and clientAppVersion. The server applies them in three places:

  • PostHog: new server-side events client.connected, client.thread.started, and client.turn.requested carry surface and app version. Client rotation, multi-client users, and per-surface thread starts become plain PostHog queries.
  • auth_sessions: new nullable client_surface and client_app_version columns, refreshed on every connect, so rows track the client's current build instead of freezing at pairing.
  • Event log: the orchestration engine stamps an optional origin into the metadata of every event a client-dispatched command produces. The decider stays pure; local per-thread attribution is now queryable in SQLite.

Every new field is optional on both ends. Old clients send nothing and everything behaves as before; old servers ignore the unknown params. No UI changes on any surface.

Tested: engine origin stamping, session store partial-update semantics (COALESCE), migration columns, plus the full server integration suite (125 tests) and typechecks across contracts, client-runtime, server, web, and mobile.

Built by Claude Fable 5 running in Claude Code.


Note

Medium Risk
Touches WebSocket handshake, auth_sessions schema, and orchestration event metadata. Attribution is optional and best-effort so connect/dispatch should not fail, but a bad migration or origin stamping bug would affect persistence and telemetry.

Overview
Clients now send optional clientSurface and clientAppVersion on the /ws upgrade so the server can tell web, desktop, and mobile apart (pairing previously lumped web and desktop as desktop).

On connect the server records those fields on auth_sessions (new nullable columns, COALESCE so partial reports never wipe earlier values), emits client.connected, and stamps the same origin onto every event produced by commands from that connection. Thread create / turn start also fire client.thread.started and client.turn.requested.

Web, desktop, and mobile populate surface/appVersion in presentation metadata; old clients and old servers stay compatible because the params and event metadata.origin are optional.

Reviewed by Cursor Bugbot for commit 36694fb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Track client surface and appVersion through websocket connections to orchestration events

  • Adds ClientSurface (web | desktop | mobile) and appVersion to client presentation metadata; web, desktop, and mobile clients now report both on connect
  • Appends clientSurface/clientAppVersion query params to websocket URLs in resolveRemoteWebSocketConnectionUrl and resolveRemoteDpopWebSocketConnectionUrl
  • Server reads origin from the /ws upgrade request, persists it to auth_sessions via SessionStore.recordClientConnection, and passes it into the RPC layer
  • OrchestrationEngine.dispatch stamps origin onto planned event metadata; analytics events for client.connected and selected commands now include origin properties
  • Migration 41 adds nullable client_surface and client_app_version columns to auth_sessions using COALESCE to preserve prior non-null values
  • Risk: recordClientConnection is best-effort and logs a warning on failure, but reviewers should verify the COALESCE logic in setClientConnectionRow (041_AuthSessionClientConnection.ts) does not overwrite existing values when new fields are absent

Macroscope summarized 36694fb.

Clients announce surface (web/desktop/mobile) and app version as optional
query params on the /ws upgrade. The server stamps them onto PostHog events
(client.connected, client.thread.started, client.turn.requested), refreshes
auth_sessions on every connect, and records the origin in orchestration
event metadata. All fields optional both ways: old clients and old servers
keep working unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1194debf-65b1-47c8-a038-0b2de8d6c14a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 21, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One convention issue found: the new AnalyticsService imports use a named tag import instead of the local service-module namespace import used everywhere else in this repo (server.ts, serverRuntimeStartup.ts, provider/Layers/ProviderService.ts). Everything else in the diff (dispatch origin plumbing, SessionStore.recordClientConnection, AuthSessionRepository.setClientConnection, migration 041, contracts schemas) follows the service, error, and dependency-acquisition conventions.

Posted via Macroscope — Effect Service Conventions

import * as ResourceAttribution from "./resourceTelemetry/ResourceAttribution.ts";
import * as ResourceTelemetry from "./resourceTelemetry/ResourceTelemetry.ts";
import * as UsageService from "./usage/UsageService.ts";
import { AnalyticsService } from "./telemetry/AnalyticsService.ts";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same namespace convention here; AnalyticsService.layerTest at line 837 keeps working since the module re-exports it.

Suggested change
import { AnalyticsService } from "./telemetry/AnalyticsService.ts";
import * as AnalyticsService from "./telemetry/AnalyticsService.ts";

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/ws.ts
import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts";
import * as ProcessResourceMonitor from "./diagnostics/ProcessResourceMonitor.ts";
import * as ResourceTelemetry from "./resourceTelemetry/ResourceTelemetry.ts";
import { AnalyticsService } from "./telemetry/AnalyticsService.ts";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a service boundary, so consider importing the service module as a namespace and using its public module shape (AnalyticsService.AnalyticsService), matching server.ts, serverRuntimeStartup.ts, and provider/Layers/ProviderService.ts — and every other local service import in this file.

-import { AnalyticsService } from "./telemetry/AnalyticsService.ts";
+import * as AnalyticsService from "./telemetry/AnalyticsService.ts";

The two acquisition sites then become yield* AnalyticsService.AnalyticsService (lines 394 and 2393).

Posted via Macroscope — Effect Service Conventions

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.4 KiB 13.4 KiB +4 B (+0.0%) 15.1 KiB
Codex Thread snapshot wire 6.9 KiB 6.9 KiB +6 B (+0.1%) 7.3 KiB
Codex Live turn WebSocket wire 6.5 KiB 6.5 KiB −2 B (−0.0%) 7.8 KiB
Codex Live turn WebSocket decoded 55.0 KiB 55.0 KiB 0 B (0.0%) 66.4 KiB
Codex Live turn messages 16 16 0 (0.0%) 21
Claude Total thread wire 13.5 KiB 13.4 KiB −14 B (−0.1%) 15.1 KiB
Claude Thread snapshot wire 6.9 KiB 6.9 KiB −3 B (−0.0%) 7.3 KiB
Claude Live turn WebSocket wire 6.6 KiB 6.5 KiB −11 B (−0.2%) 7.8 KiB
Claude Live turn WebSocket decoded 55.8 KiB 55.8 KiB 0 B (0.0%) 66.4 KiB
Claude Live turn messages 16 16 0 (0.0%) 21

Baseline: be7d35a · PR result: 36694fb · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 36694fb. Configure here.

Comment thread apps/server/src/ws.ts
ORCHESTRATION_WS_METHODS.dispatchCommand,
Effect.gen(function* () {
const normalizedCommand = yield* normalizeDispatchCommand(command);
yield* recordClientCommandAnalytics(normalizedCommand);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Analytics fires before dispatch succeeds

Medium Severity

recordClientCommandAnalytics runs before dispatchNormalizedCommand completes. Failed creates, turns, and bootstrap flows (including cleanup that deletes a just-created thread) still emit client.thread.started and client.turn.requested, so PostHog surface counts no longer match persisted threads and turns.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 36694fb. Configure here.

@macroscopeapp

macroscopeapp Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a new client origin tracking feature with database schema changes, new analytics events, and modifications to the core orchestration dispatch flow. Additionally, there is an unresolved medium-severity finding about analytics firing before dispatch succeeds. Human review is warranted.

You can add or adjust custom eligibility rules. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant