Skip to main content

Diagnostics

kiln keeps a record of its own misbehaviour. Errors that used to flash past in the squad feed or a chat bubble now also land in a persistent log, crashes leave a trace, and both are easy to mine — by you, by the squad, or by anything that can read a file.

The trove

Everything lives under ~/Library/Application Support/kiln/diagnostics/:

  • log.jsonl — one JSON line per event: timestamp, level (info/warn/error/crash), area, message, and optional detail. Capped at 1 MB with one rotation to log.1.jsonl, so it never grows without bound.
  • crashes/ — one stamped file per crash, with the exception or signal name and a backtrace.
  • perf.jsonl — one JSON line per flushed window of hot-path timings (see Performance below). Capped at 500 KB with one rotation to perf.1.jsonl.
  • metrics/ — one stamped JSON file per MetricKit daily payload (see Performance below).

Entries also mirror to the unified log (subsystem com.mcclowes.kiln), so Console.app sees them live.

View → Diagnostics Log opens log.jsonl in an editor tab, like any other file. JSONL is deliberate: grep, jq, and agents all read it without a viewer.

What gets logged

The error paths that used to be silent or ephemeral now write through Diag:

  • squad errors, including steward and ambient-review failures that never surface in the feed
  • assistant stream failures and weave (intent edit) failures
  • jobs that fail to start
  • session lifecycle: clean exits, unclean exits, crashes

Crash reporting

At launch kiln installs an uncaught-exception handler and async-signal-safe handlers for the fatal signals (SIGABRT, SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP). A crashing process writes the signal name and a backtrace to a pre-opened file using only write(2) — no allocation, nothing that can deadlock mid-crash — then re-raises so macOS still produces its own report.

A session sentinel covers the rest: kiln arms it at launch and clears it on clean exit. If the sentinel survives to the next launch with no crash trace, that session is logged as an unclean exit (force kill, power loss, watchdog).

Crashes the in-process handlers can't catch still land in ~/Library/Logs/DiagnosticReports as .ips files; kiln scans for its own at startup.

Performance

kiln watches whether it stays fast and small, with the same "ships in release, near-zero cost" philosophy as the crash handlers.

  • Signposts. OSSignposter intervals wrap the three hot paths — syntax highlighting (Highlighter.tokens), branch-diff computation (BranchDiff.load), and one-shot LLM calls (Potter.oneShot). Attach Instruments and filter on subsystem com.mcclowes.kiln for the live timeline. Signposts are untraced when no tool is attached, so they cost nothing in normal use.
  • Persisted timings. Each interval also tees its duration into perf.jsonl. Samples accumulate in a bounded in-memory reservoir per metric and flush a single summary line (count plus p50/p95/max) every 200 observations — the editor's highlight path never touches disk. The partial window flushes on clean exit. This is what answers "did highlighting get slower this week?" after the fact, without an Instruments trace.
  • MetricKit. kiln subscribes to the OS's daily, privacy-preserving digest — app launch time, hang rate, peak memory, and CPU — and folds each payload into log.jsonl (a warn past a launch/hang/memory budget, otherwise info), parking the full JSON under metrics/. The OS aggregates on its own schedule and hands payloads over roughly once a day, so there's no runtime cost and nothing leaves the device.

The squad survey includes both the recent errors and the freshest per-metric timings, so an architect planning work knows what's been slow lately.

Bundle size

CI reports the built Kiln.app size on every PR (a table in the run's step summary) and fails past a budget, so "small" can't regress silently — scripts/bundle-size.sh, tunable with MAX_BUNDLE_MB. The release build reports its real (smaller) size too, without ever blocking a release on it.

Mining it

The squad survey includes the most recent errors, the same way it includes the notepad and the decision log — so an architect planning work knows what's been going wrong lately. Outside the app, the trove is just files:

jq -r 'select(.level == "error") | "\(.ts) [\(.area)] \(.message)"' \
~/Library/Application\ Support/kiln/diagnostics/log.jsonl