feat(diagnostics): opt-in activity trace - #143
Merged
Merged
Conversation
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.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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=1writes 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-sessionspayload, 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
jqrecipes in docs/activity-trace.md; rationale in.ai/contexts/ipc-bridge.md.