Skip to content

[Bug]: Idle clients export a no-op platform reconcile trace every 3 seconds #26484

Closed

Description

@sscarduzio

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/web

Steps to reproduce

  1. Run vp run dev on main (48a2923) with a fresh home dir, pair the browser, and leave the web app idle on a thread. Or leave the 0.1.3 desktop app idle.
  2. Watch DevTools Network for POST /api/observability/v1/traces, or tail userdata/logs/server.trace.ndjson for EnvironmentRegistry.reconcilePlatform.

Expected behavior

When the environment topology hasn't changed, an idle client doesn't export traces apart from the periodic web.backgroundActivity.report.

Actual behavior

The web platform connection source polls topology every 3 seconds (PLATFORM_POLL_INTERVAL in apps/web/src/connection/platform.ts). layer.ts passes every result to EnvironmentRegistry.reconcilePlatform, even when the poll returns the same cached registrations. Each no-op run starts four spans: reconcilePlatform → installPlatformRegistration → withLeaseLock → installEntryLocked. These are parented under the long-lived clientRuntime.connection.application.start span. As a result, an idle client exports a trace about every 3 seconds, and the server appends it to server.trace.ndjson.

Measured on main, idle for 60 s, with a probe counting fetches and the OTLP span names they carry:

trace POSTs reconcilePlatform chains
Web (Chrome, visible and focused) 22 15-20
Desktop dev app (Electron, window hidden) 16 15

In the server trace file, with a thread open and nothing happening, spans rooted in this reconcile were 160 of 912 spans and 178 KB of 550 KB written per 2 minutes. That makes them the largest idle source by bytes, so they also rotate useful traces out of the 10 MB trace log sooner. v0.1.2 behaves the same way.

The repeated reconcile also acts as a retry. When a best-effort write fails, the next identical poll runs it again. Those writes are clearing PR routing permission, storing a platform bearer, and removing a shadowed persisted registration. The registry.test.ts case "retains a healthy runtime when the platform repeats an identical registration" covers this. So simply deduplicating the stream isn't a safe fix.

Proposed fix (I have a branch ready, with a test that fails on main):

  • The registry remembers the last registrations that applied fully.
  • It skips an identical repeat without starting a span.
  • Those best-effort writes report failure, so a failed write still gets retried on the next poll.

With it, the same web run drops to 3 trace POSTs in 78 s, all of them the activity report, and 0 reconcile spans.

Related:

Impact

Minor bug or occasional failure

Version or commit

main @ 48a2923; also reproduced on v0.1.2

Environment

macOS on Apple Silicon: vp run dev with Chrome, and the Electron dev app. I first noticed it in the Linux desktop app running in a QEMU VM, where every idle wakeup costs host battery.

Logs or stack traces

# server.trace.ndjson, last 120 s idle on main, spans grouped by root (count, bytes)
240 120598 Scheduler.register
221  88058 VcsStatusBroadcaster.retainRemotePoller
160 177840 clientRuntime.connection.application.start   # the client's 3 s reconcile
 83  36621 ProviderInstanceRegistry.makeProviderInstanceRegistry
 45  50192 web.backgroundActivity.report

Workaround

None.

Activity

  1. sscarduzio commented on Oct 28, 2026

    @sscarduzio
    Author

    I re-measured this on a production build, since the numbers above came from short dev-mode samples.

    Setup:

    • One t3 serve build (vp pack). The server code is identical between the two variants; only the web bundle differs: A = main 48a2923, B = the proposed fix.
    • Each run used a fresh home dir with a one-file git project, and a fresh headless Chrome (visible, focus emulated) idling on the home route.
    • 3 min warm-up, then a 10 min window, run in ABBA order on an Apple Silicon Mac.
    • Measured with proc_pid_rusage over the server process tree and the whole Chrome process tree.

    Per minute, mean of 2 runs per variant:

    main fix change
    Server CPU 344 ms (355, 333) 249 ms (258, 240) −28%
    Server wakeups 929 734 −21%
    Server disk writes 729 KB 412 KB −43%
    Browser CPU, all processes 388 ms (373, 402) 256 ms (228, 283) −34%
    Renderer CPU 182 ms 121 ms −33%
    Trace bytes written 196 KB 108 KB −45%
    reconcilePlatform spans 20 0

    In absolute terms the saving is small: about 0.4% of one core across server and browser. Relative to the rest of the idle work it's large, though: about a third of idle client CPU, a quarter of idle server CPU, and 43% of idle server disk writes (~450 MB/day). The A and B runs don't overlap.

    Not covered:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions