Plugin SDK & Hooks

Configuration

Where the Trenchcoat plugin keeps credentials and settings, and what each setting does.

The plugin splits its configuration across two files, deliberately:

FileHoldsWhy separate
~/.claude/settings.jsonCredentials and telemetry env varsClaude Code loads env here into every session. Secrets belong in one place you already protect.
~/.claude/trenchcoat/config.jsonNon-credential settingsCapture behavior and retention. Safe to read, diff, or check into a dotfiles repo.

No credential is ever stored in config.json. If you find an api_key there, something wrote it by mistake — move it to settings.json and delete it.

Credentials

Set in the env block of ~/.claude/settings.json:

{
  "env": {
    "TRENCHCOAT_API_KEY": "ct_live_...",
    "TRENCHCOAT_API_URL": "https://app.trenchcoat.io"
  }
}
VariableRequiredDefaultDescription
TRENCHCOAT_API_KEYyesYour API key, from Settings → API Keys.
TRENCHCOAT_API_URLnohttps://app.trenchcoat.ioOnly set this when self-hosting or pointing at a staging instance.

/trenchcoat:connect reads the key from this file and configures everything else around it. It never asks you to paste a key into the conversation — that would put it in your session transcript.

Environment variables in settings.json are loaded when a Claude Code session starts. After editing them you must start a new session — the running one keeps the old values.

/trenchcoat:doctor reports the running session's environment, so it will report stale values until you restart. That is the single most common reason doctor appears to contradict a connect that just succeeded.

Settings

~/.claude/trenchcoat/config.json is created with defaults on first run:

{
  "enabled": true,
  "privacy": {
    "log_prompt_content": false,
    "tool_input_preview_chars": 100,
    "capture_git_branch": false
  },
  "retention_days": 30,
  "push_batch_size": 100
}
FieldTypeDefaultDescription
enabledbooleantrueMaster switch. When false, hooks fire but record nothing.
privacy.log_prompt_contentbooleanfalseWhen false, prompt events carry only prompt_length and word_count — never the text.
privacy.tool_input_preview_charsinteger100How many characters of a tool's input to keep in tool_start.input_preview. 0 disables the preview.
privacy.capture_git_branchbooleanfalseOpt-in. The dashboard's branch filter stays empty until you enable it. Off by default because branch names can reveal business intent in a way a working-directory path usually doesn't.
retention_daysinteger30How long local JSONL files are kept.
push_batch_sizeinteger100Events per batch when pushing to the API. The API accepts up to 1000 per request.

Edits take effect the next time a hook fires — no restart needed. This is the opposite of the credential rules above, because config.json is read from disk on each hook while settings.json env vars are loaded once per session.

Tool results are never captured, and there is no setting for it. tool_end records result_size — the length of the response — plus whether the tool errored and, only when it did, up to 200 characters of the error message. The result body itself never leaves your machine by this path. Earlier versions listed a privacy.log_tool_results key here; nothing ever read it, and the behaviour it described was already unconditional. The key is gone rather than made real: a switch to start shipping tool output would be a new capability, not a fix.

Content capture is gated server-side

The four OTEL_LOG_* variables that /trenchcoat:connect writes control what Claude Code sends. What is actually stored is decided server-side by your API key's scopes.

That means editing those variables by hand does nothing useful: the server strips any content the key is not scoped for, regardless. To change what is captured, change the key's scopes in Settings → API Keys, then re-run /trenchcoat:connect — it reads the key's scopes and sets the local flags to match.

See Authentication for what each scope records.

Commands

The plugin registers six commands and one skill.

CommandWhat it does
/trenchcoat:connectReads your key from settings.json, validates it, and writes the telemetry configuration. Refuses to overwrite a different telemetry backend without asking.
/trenchcoat:disconnectReverses connect, removing what it added from settings.json. Local JSONL files are untouched.
/trenchcoat:doctorFull diagnostic — credentials, connectivity, event coverage, OTel configuration, scope agreement.
/trenchcoat:verifyQuick check that the setup is working.
/trenchcoat:reportText summary of locally recorded data.
/trenchcoat:dashboardGenerates and opens a local HTML dashboard with charts.
telemetry-insights skillAnalyzes local telemetry and answers questions about usage patterns in natural language.

connect and disconnect are the only two that write to settings.json. doctor, verify, report, and dashboard are read-only.

Exit codes

connect runs lib/connect.py, which uses numeric exit codes so the command can branch on them reliably:

CodeMeaning
0Success
2Usage error
3A different telemetry backend is already configured; nothing was changed
6The API could not be reached, or rejected the key
7No API key configured, or settings.json is malformed or unwritable

Exit 3 is a refusal, not a failure: if OTEL_EXPORTER_OTLP_ENDPOINT already points somewhere that isn't your Trenchcoat instance, connect changes nothing and asks first. Re-run with --overwrite-otel to replace it.

On exit 6, the reason field distinguishes a rejected key (key_rejected — regenerate it; retrying will not help) from a genuine connectivity problem (retry is reasonable).

Self-hosting

Point at your own instance by setting TRENCHCOAT_API_URL in settings.json and re-running /trenchcoat:connect. It derives the OTel endpoint from that URL.

When you switch back to the hosted instance, remove TRENCHCOAT_API_URL — or run connect with --api-url https://app.trenchcoat.io, which clears the override for you. Leaving a stale value behind sends local event pushes to the old instance while OTLP export goes to the new one.

On this page