Skip to content

feat(diagnostics): opt-in activity trace - #143

Merged
devsuitup merged 1 commit into
mainfrom
feat/activity-trace
Aug 22, 2026
Merged

feat(diagnostics): opt-in activity trace#143
devsuitup merged 1 commit into
mainfrom
feat/activity-trace

Conversation

@devsuitup

Copy link
Copy Markdown
Owner

Why

Three activity bugs were fixed this week by reading code and reasoning about it (#141, #142). Each time, the decisive question was "what actually happened, in what order, across both processes" — and there was no way to answer it. Activity state is produced in the main process, consumed in the renderer, and rendered through several paths; when the indicator lies, nothing records why.

This adds an opt-in trace that answers that question.

What

SWITCHBOARD_ACTIVITY_TRACE=1 writes a single ordered JSONL file next to the database. The main process is the only writer: it holds a monotonic sequence counter and timestamps every entry, and the renderer ships its events over a dedicated IPC channel rather than writing its own file. Two writers with two clocks would produce an unorderable trace, which is the one thing this tool cannot afford.

It records the raw OSC detection (including the actual code points received in the terminal title, and the decision the detector made about them), progress-bar events, every emission — and every emission suppressed because the state had not changed, which is itself information — subagent spawn/heartbeat/completion/rehabilitation, forks, the get-active-sessions payload, every renderer state mutation with before/after values, and every class applied to or removed from a sidebar item.

Any probe that reports an event carries sent, computed from the same window-liveness guard the real IPC send uses, so the trace can never claim a delivery that did not happen.

Cost when disabled

Nothing. The flag is resolved once at require time and every probe is guarded at the call site (if (TRACE) trace({...})), so the payload object is never constructed; the IPC handler is not even registered. The renderer learns the flag through a command-line argument rather than re-reading the environment, since the preload is sandboxed. Verified probe by probe in review, and by tests that pass a getter-instrumented payload and assert it is never read.

Bounded

Four rotating 16 MB segments (64 MB ceiling, configurable). A segment is only dropped from tracking once its unlink actually succeeds, so a locked file is retried rather than silently leaked — with a one-shot warning line in the trace itself.

Tests

task check: 649 tests, 642 pass, 0 fail, 7 pre-existing skips, 0 lint errors. The suite also runs clean with the trace enabled (one test fails by construction — the one asserting the singleton is off in a plain test process), which proves no probe body throws.

Usage, categories and jq recipes in docs/activity-trace.md; rationale in .ai/contexts/ipc-bridge.md.

The sidebar's activity indicators are driven by two processes with two
clocks, and their failures are ordering failures: a front emitted but not
received, a poll reply overwriting a fresher event, a fork re-keying
halfway. Nothing recorded that sequence — most of the OSC handling logs at
debug, which a packaged build never writes, and the renderer side was not
logged at all.

Add a trace that records both processes into one ordered JSONL file. The
main process is the only writer: it holds the sequence counter and stamps
every entry, and the renderer forwards its probes fire-and-forget over a
new `activity-trace` IPC. Probes cover OSC 0 / OSC 9;4 detection with the
title's leading code points in hex, every cli-busy-state emission and
suppression, subagent spawn/heartbeat/completion, forks, the
get-active-sessions payload, and on the renderer side every event
reception, state-store mutation, class write and poll reconciliation.

Off unless SWITCHBOARD_ACTIVITY_TRACE is set. The flag resolves once at
require time into a constant, so probe sites are `if (TRACE) ...` and no
payload literal is evaluated when the trace is off; the IPC handler is not
registered either. Output goes to the app data directory under a
timestamped name, bounded at 64 MB across four rotating segments.
@devsuitup
devsuitup merged commit 44e6593 into main Aug 22, 2026
7 checks passed
@devsuitup
devsuitup deleted the feat/activity-trace branch August 22, 2026 15:53
@devsuitup devsuitup mentioned this pull request Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant