Diagnostics

August 29, 2026 · View on GitHub

dsh-local-link keeps a small local history of failed connection and UI actions. It exists so an npm installation can be debugged without enabling verbose logs or reproducing the problem under a debugger. It is not telemetry and has no upload path.

What is recorded

An event is created only when an operation fails or a request is rejected:

  • generating a one-time connection code;
  • consuming an expired, replaced, reused, or malformed invitation;
  • opening the gateway from an unpaired browser or an invalid network/Host boundary;
  • reaching the loopback Harness HTTP server or event WebSocket;
  • rewriting the authenticated Harness index;
  • copying the one-time link;
  • renaming or revoking a paired device;
  • recovering an unreadable diagnostics file.

Successful starts, requests, pairing, link copies, renames, revocations, and ordinary UI clicks are never recorded. Diagnostics are event-driven: there is no sampling loop, heartbeat, analytics client, or remote collector.

The QR popover separately checks every two seconds whether its visible one-time invitation has been consumed. That short-lived status check runs only while the popover is open and neither reads nor writes diagnostics.

Retention and I/O

  • Default file: ~/.dsh/local-link/diagnostics.json.
  • Default retention: the latest 15 events; configurable from 5 through 200.
  • Settings view: the latest 12 events.
  • Repeated identical events inside a five-second burst are stored once.
  • Opening Settings → Local access reads the report once. Refresh is the only repeated UI read.
  • The file is written only for a retained failure, an explicit clear, or migration away from unreadable/obsolete entries.
  • Background event writes use an atomic temporary-file replacement and fail soft; diagnostics cannot take down pairing or proxy traffic. The explicit Clear action reports a persistence failure instead of claiming that the on-disk report was removed.

Set diagnosticsEnabled: false to stop retaining new events. Clear immediately removes the retained history.

Privacy contract

Every event contains only:

  • a random event ID;
  • an ISO timestamp;
  • warn or error severity;
  • a stable event code;
  • optional context restricted to reason, method, and requestKind.

Context strings are stripped of control characters and limited to 80 characters. The store rejects unknown event codes and removes obsolete or malformed entries when it loads.

Reports never contain pairing tokens, cookies, credentials, IP addresses, device or session IDs, device names, URLs, request paths, exception messages, stack traces, prompts, conversations, workspace names, or project files. Client-side reporting accepts only three fixed error codes and cannot submit arbitrary text.

Collect a report

  1. Reproduce the failed action once.
  2. Open Settings → Local access → Diagnostics.
  3. Click Refresh if the panel was already open during reproduction.
  4. Inspect the newest code and timestamp.
  5. Click Copy report.
  6. Review the JSON before attaching it to an issue.

The copied report wraps the retained events with a schema version and export timestamp:

{
  "schemaVersion": 1,
  "exportedAt": "2026-08-28T00:00:00.000Z",
  "events": [
    {
      "id": "generated-event-id",
      "at": "2026-08-28T00:00:00.000Z",
      "level": "error",
      "code": "HTTP_UPSTREAM_ERROR",
      "context": { "requestKind": "root" }
    }
  ]
}

Event reference

CodeTriggerFirst check
PAIRING_GENERATION_FAILEDGenerate another code could not produce an invitation or QR image.Confirm the desktop Harness process is healthy, then retry once.
PAIRING_REJECTEDThe invitation was expired, replaced, or already consumed.Generate a new code; each invitation is one-use.
PAIRING_INVALIDThe phone submitted malformed pairing data or device state could not be saved.Generate a new code; if it repeats, inspect file permissions for ~/.dsh/local-link/.
REQUEST_REJECTEDSource-address or Host validation rejected the LAN request.Confirm both devices use the same private network and the QR URL contains the computer's current private address.
AUTH_REQUIREDAn unpaired browser opened a protected gateway page.Pair that browser with a fresh QR/link; direct :3088 access is intentionally rejected.
HTTP_UPSTREAM_ERRORThe gateway could not reach Harness on 127.0.0.1:3080.Confirm dsh web is running and upstreamOrigin matches its port.
BROWSER_AUTH_HANDOFF_FAILEDHarness returned an invalid browser-authentication handoff target.Confirm the installed Harness version is supported and restart dsh web.
INDEX_REWRITE_ERRORThe returned Harness index did not match the supported boot contract.Confirm the installed Harness version appears in the compatibility matrix and restart dsh web.
WS_REJECTEDA WebSocket upgrade failed trust, authorization, or path validation.Re-pair the browser and verify no proxy rewrites the URL.
WS_UPSTREAM_ERRORThe gateway could not open the loopback Harness event socket.Confirm the desktop Harness process is still running and reload the paired browser.
CLIPBOARD_COPY_FAILEDThe browser refused or failed to copy the one-time link.Grant clipboard permission or select and copy the displayed link manually.
DEVICE_REVOKE_FAILEDA requested device revocation failed.Refresh the device list and retry; inspect local state-file permissions if it repeats.
DEVICE_RENAME_FAILEDA requested device rename failed.Refresh the device list and retry with a non-empty name.
DIAGNOSTICS_STATE_RESETThe previous diagnostics file was unreadable and was replaced safely.Usually no action; inspect disk health or permissions if it returns.

Maintainer rules

  • Record failures and rejections only; do not add success or lifecycle noise.
  • Add a stable code instead of persisting exception messages.
  • Add context only through SAFE_CONTEXT_KEYS in src/diagnostics.ts.
  • Never add request headers, URLs, filesystem paths, identifiers, user-entered text, or user content.
  • A diagnostics write failure must never block pairing or proxy traffic.
  • Update this reference and the privacy/retention tests whenever an event changes.