Skip to content

Ship diagnostics behind a launch switch - #123

Merged
AndrewMcLachlan merged 2 commits into
mainfrom
feat/diagnostics-switch
Aug 9, 2026
Merged

Ship diagnostics behind a launch switch#123
AndrewMcLachlan merged 2 commits into
mainfrom
feat/diagnostics-switch

Conversation

@AndrewMcLachlan

@AndrewMcLachlan AndrewMcLachlan commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Moves the diagnostics work off the debug branch and into main, behind a launch switch, so the recorder is available in a shipped build without being surfaced to everyone.

Why a launch switch and not a setting

The recorder exists for chasing a specific intermittent fault. A checkbox for that in everyone's Preferences window is clutter for the people who will never touch it, and implies an ongoing choice where there isn't one.

So there is no UI, no preference, and nothing in state.json. An ordinary launch records nothing at all.

CODEHERD_DIAGNOSTICS=1     # or --diagnostics

On Windows, prefer the environment variable over adding --diagnostics to the Start Menu shortcut. squirrel-startup.ts recreates that shortcut on --squirrel-updated, so arguments added to it are wiped at the next upgrade — silently, and exactly when you have been waiting weeks for a fault to recur.

setx CODEHERD_DIAGNOSTICS 1

What it records

Written to diagnostics.log in the app-data folder (Help → Open Diagnostics Folder).

  • event-loop stalled 1240ms (output this window: 512 chunks, 210000 bytes) — the stall together with the PTY volume around it, which is what separates output saturation from a blocking call
  • input tab=… <ESC>[B lag=4820ms — how long a keystroke waited between leaving the renderer and being serviced in main. A large lag means main was blocked; a near-zero lag next to an unresponsive prompt moves the search downstream
  • clipboard.write ok len=182 attempt=1 / FAILED … — and the absence of a line after a copy is itself the signal
  • transcript.fullread 240000000 bytes in 4200ms — the synchronous whole-file reads that stall every tab in a window
  • heartbeat: … in last 60s — so "no stalls" can be read against real throughput rather than silence

It never records what you type

The version on the debug branch logged the actual characters. That was fine for a personal branch and not fine for something shipped, because a terminal receives passwords, tokens, and private conversation, and this log is meant to be attached to an issue.

Control and escape sequences are kept verbatim — an arrow key is the entire point of a latency trace and carries no content — while printable runs collapse to a count:

Input Logged
\x1b[B (down arrow) <ESC>[B
hunter2 <7 chars>
\x1b[Bpassword\r <ESC>[B<8 chars><CR>
\x1b[200~secret-token\x1b[201~ <ESC>[200~<12 chars><ESC>[201~

It can be left armed indefinitely

Rotates at 5 MB keeping one previous file (~10 MB ceiling). It writes only when something is worth seeing — a stall, an input event, a clipboard result, or the once-a-minute heartbeat — rather than a line per sampling window, which would both flood the file and add main-process work to the very thing being measured.

Kept separate from --pty-debug

--pty-debug captures every byte the terminal shows, including anything secret on screen, and stays a developer tool. Sharing one switch would have made the safe thing carry the unsafe thing's risk. Tested in both directions: neither flag turns on the other. Raw capture does still start the timeline, since anyone debugging that deeply wants both.

Verification

npm run typecheck clean, npm test 198 passed (+17), npm run build clean.

describeInput and both flag predicates are pure and tested — arrows, SS3, SGR mouse reports, bracketed paste, named and unnamed control characters, non-ASCII, empty input, and the flag independence above.

Not covered by tests: the actual recording needs a live Electron run, so it is verified by inspection. Worth confirming once with the variable set that diagnostics.log appears and a heartbeat line lands within a minute.

docs/INSTALL.md documents turning it on and off, what it does and does not record, and the shortcut caveat.


Earlier revisions of this PR added a Preferences checkbox. That was the wrong shape — the capability needed to be present in a shipped build, not exposed to every user — and has been removed.

AndrewMcLachlan and others added 2 commits August 8, 2026 07:14
The input-latency fault is rare and unpredictable, so it has to be recorded on the
run where it happens. Until now that meant running a dev build from a branch, which
in practice means never having it on when it matters.

Adds a "Record diagnostic log" preference, off by default, that starts and stops the
recorder immediately rather than at next launch. It logs event-loop stalls with the
PTY output volume around them, how long each keystroke waited between leaving the
renderer and being serviced, clipboard write outcomes, and whole-transcript reads —
enough to tell output saturation from a blocking call from input that never arrived.

Two changes were needed before this could ship rather than stay on a branch.

It no longer records what you type. A terminal receives passwords, tokens, and
private conversation, and the log is meant to be attached to a bug report. Control
and escape sequences are kept verbatim, because an arrow key is the whole point of a
latency trace and carries no content; printable runs collapse to a count.

It no longer grows without bound, rotating at 5 MB and keeping one previous file, so
it can be left running for weeks waiting for a recurrence.

Raw PTY capture stays behind --pty-debug and is deliberately not on this switch: it
records every byte the terminal shows and remains a developer tool.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A setting in everyone's Preferences window is clutter for the people who will
never use it, and the recorder exists for chasing a specific intermittent fault
rather than as something to leave configured. Drop the preference and start on
CODEHERD_DIAGNOSTICS=1 or --diagnostics instead.

Kept separate from --pty-debug in both directions: raw capture records every byte
the terminal shows and stays a developer tool, while the timeline is safe enough
to leave running. Raw capture does still imply the timeline, since anyone
debugging that deeply wants both.

Documented in INSTALL.md, recommending the environment variable over a shortcut
argument on Windows: our own Squirrel handler recreates the Start Menu shortcut on
update, so arguments added to it are lost at the next upgrade.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AndrewMcLachlan AndrewMcLachlan changed the title Ship diagnostics as a preference that can be left armed Ship diagnostics behind a launch switch Aug 9, 2026
@AndrewMcLachlan

Copy link
Copy Markdown
Owner Author

Reworked — the Preferences checkbox is gone. You wanted the capability present in a shipped build, not exposed to everyone, and a setting for chasing one intermittent fault is clutter for everybody who will never touch it.

Now: CODEHERD_DIAGNOSTICS=1 or --diagnostics. No UI, no preference, nothing in state.json. An ordinary launch records nothing.

One caveat worth knowing before you edit your shortcut: prefer the environment variable on Windows. squirrel-startup.ts recreates the Start Menu shortcut on --squirrel-updated, so arguments added to the shortcut are wiped at the next upgrade — silently, and exactly when you have been waiting weeks for a recurrence. setx CODEHERD_DIAGNOSTICS 1 survives updates.

Kept from the earlier version, because both still matter for a log you might attach to an issue:

  • Never records what you type<ESC>[B for an arrow key, <7 chars> for typed text.
  • Rotates at 5 MB, one previous file, so it can stay armed indefinitely.

--pty-debug remains separate in both directions and still implies the timeline, since anyone debugging that deeply wants both.

docs/INSTALL.md documents how to turn it on and off, what it does and does not record, and the shortcut caveat.

typecheck clean, 198 tests (+17), build clean.

@AndrewMcLachlan
AndrewMcLachlan merged commit 15430ff into main Aug 9, 2026
4 checks passed
@AndrewMcLachlan
AndrewMcLachlan deleted the feat/diagnostics-switch branch August 9, 2026 08:32
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