Skip to content

Debugging Guide

Jacob Fu edited this page May 30, 2026 · 4 revisions

Debugging Guide

Enable Debug Mode

Pass -cotabby-debug as a launch argument in Xcode:

  1. Product > Scheme > Edit Scheme > Run > Arguments
  2. Add -cotabby-debug to "Arguments Passed On Launch"

This enables CotabbyDebugOptions.isEnabled, which turns on:

  • On-disk JSONL log sinks (see below) in addition to the always-on Console.app stream
  • Detailed service-level logging (prompt input, raw model output, normalized output)
  • Verbose focus-tracking diagnostics

Debug logging is gated so that user content never appears in production logs.

Structured Logs (JSONL)

With -cotabby-debug enabled, Cotabby writes structured JSON-per-line logs that are designed to be filtered with jq:

  • ~/Library/Logs/Cotabby/cotabby.jsonl — main event stream. All metadata (request IDs, engine names, token counts, latencies, error reasons) is flattened as top-level fields.
  • ~/Library/Logs/Cotabby/llm-io.jsonl — full LLM prompts and completions, one record per generation. Shares request_id with the main log so a single suggestion can be joined across files.
  • ~/Desktop/cotabby-ax-dump.txt — most recent Chrome AX tree snapshot. Overwritten on each Chrome focus change.
  • Rotated previous logs: *.jsonl.1 (one-step rotation at ~10 MB).

Correlation IDs. Every prediction gets a request_id like req_a3f9k2lq, stamped on every log line that touches that request. To pull the complete history of one suggestion:

jq 'select(.request_id == "req_a3f9k2lq")' ~/Library/Logs/Cotabby/cotabby.jsonl
jq 'select(.request_id == "req_a3f9k2lq")' ~/Library/Logs/Cotabby/llm-io.jsonl

Useful recipes:

# Recent errors across the app
jq 'select(.level == "error")' ~/Library/Logs/Cotabby/cotabby.jsonl

# Llama generations slower than 500 ms
jq 'select(.engine == "llama" and .latency_ms > 500)' ~/Library/Logs/Cotabby/llm-io.jsonl

# Coordinator state transitions
jq 'select(.category == "suggestion" and .stage != null)' ~/Library/Logs/Cotabby/cotabby.jsonl

# Runtime model load/decode events
jq 'select(.category == "runtime")' ~/Library/Logs/Cotabby/cotabby.jsonl

Symptom to category map (jump straight to the right filter):

  • Ghost text didn't appear: suggestion + focus
  • Wrong text inserted: look up the request in llm-io.jsonl, then walk suggestion for acceptance
  • Model won't load / decode fails: runtime + models
  • Permission dialog loop: app (permission state transitions)
  • Chrome-specific weirdness: start with ~/Desktop/cotabby-ax-dump.txt, then focus
  • Wrong backend chosen: suggestion router selection log (engine, fallback_engine)

Console.app fallback (when -cotabby-debug wasn't set):

log show --predicate 'subsystem == "com.cotabby.app"' --last 10m
log stream --predicate 'subsystem == "com.cotabby.app"' --level debug

Ghost Text Does Not Appear

Work through this decision tree:

1. Check the menu bar. Open Cotabby's menu - does the status say anything useful?

2. Is Cotabby disabled?

  • Globally turned off? Check the "Enable Globally" toggle.
  • App-disabled? The focused app may be in the disabled list. Check Settings > Disabled Apps.
  • Terminal? Cotabby automatically blocks terminal emulators (TerminalAppDetector).
  • Secure field? Cotabby refuses to operate on password fields.
  • Missing permissions? Input Monitoring and Accessibility must both be granted.

All these checks live in SuggestionAvailabilityEvaluator.disabledReason().

3. Is the field recognized?

  • Check FocusSnapshot.capability - is it .supported?
  • If .unsupported or .blocked, the AX tree isn't exposing a usable text field. See App Compatibility Notes.

4. Is generation running?

  • With -cotabby-debug, check Xcode console for prompt/generation output.
  • If the model isn't loaded, check LlamaRuntimeManager bootstrap state in the menu bar.

5. Is the result being suppressed?

  • SuggestionTextNormalizer strips empty or prefix-echoing output.
  • SuggestionSessionReconciler invalidates sessions when editor content diverges from the expected state.

Ghost Text Appears in the Wrong Position

Check the caret geometry quality:

Quality Meaning Accuracy
.exact App provides precise BoundsForRange Pixel-accurate
.derived Computed from child frame geometry Usually close
.estimated Fallback from field frame Approximate

If .estimated, the host app doesn't expose precise caret bounds via Accessibility. Check AXTextGeometryResolver and GhostSuggestionLayout.

Suggestions Are Stale or Repeat What I Typed

Two systems prevent this:

  • SuggestionTextNormalizer strips leading text that echoes the prefix.
  • SuggestionSessionReconciler invalidates sessions when the editor text has changed since generation started.

Check both if you see stale or repeated output.

Model Crashes or Produces Empty Output

  • Empty output: Check -cotabby-debug console for the raw model response. The normalizer may be stripping everything. If the model returns thinking tokens (e.g., <think>) the prompt should suppress them (see LlamaPromptRenderer).
  • Crashes: Check for ggml_abort in the crash log - this usually means an oversized prompt exceeded the context window. Verify model file integrity with ModelFileValidator.

Chrome / Electron Apps Behave Differently

Chrome and Electron apps recycle Accessibility node identifiers (CFHash values) between polls. This makes elementIdentifier unreliable for tracking which field is focused.

Cotabby works around this using processIdentifier + focusChangeSequence instead. If you're debugging focus identity issues in these apps, check:

  • SuggestionSessionReconciler - session continuity logic
  • ContextBuffer - context accumulation across polls

Permissions Granted But Nothing Happens

  • Restart the app. PermissionManager checks are cached at startup.
  • Check signing. When running from Xcode, your development team must be configured for entitlements to activate. If the app isn't properly signed, macOS may silently deny Accessibility access.
  • Check System Settings. Verify Cotabby appears in Privacy & Security > Accessibility and Privacy & Security > Input Monitoring with checkmarks.