Configuration
Where the Trenchcoat plugin keeps credentials and settings, and what each setting does.
The plugin splits its configuration across two files, deliberately:
| File | Holds | Why separate |
|---|---|---|
~/.claude/settings.json | Credentials and telemetry env vars | Claude Code loads env here into every session. Secrets belong in one place you already protect. |
~/.claude/trenchcoat/config.json | Non-credential settings | Capture 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"
}
}| Variable | Required | Default | Description |
|---|---|---|---|
TRENCHCOAT_API_KEY | yes | — | Your API key, from Settings → API Keys. |
TRENCHCOAT_API_URL | no | https://app.trenchcoat.io | Only 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
}| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch. When false, hooks fire but record nothing. |
privacy.log_prompt_content | boolean | false | When false, prompt events carry only prompt_length and word_count — never the text. |
privacy.tool_input_preview_chars | integer | 100 | How many characters of a tool's input to keep in tool_start.input_preview. 0 disables the preview. |
privacy.capture_git_branch | boolean | false | Opt-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_days | integer | 30 | How long local JSONL files are kept. |
push_batch_size | integer | 100 | Events 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.
| Command | What it does |
|---|---|
/trenchcoat:connect | Reads your key from settings.json, validates it, and writes the telemetry configuration. Refuses to overwrite a different telemetry backend without asking. |
/trenchcoat:disconnect | Reverses connect, removing what it added from settings.json. Local JSONL files are untouched. |
/trenchcoat:doctor | Full diagnostic — credentials, connectivity, event coverage, OTel configuration, scope agreement. |
/trenchcoat:verify | Quick check that the setup is working. |
/trenchcoat:report | Text summary of locally recorded data. |
/trenchcoat:dashboard | Generates and opens a local HTML dashboard with charts. |
telemetry-insights skill | Analyzes 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:
| Code | Meaning |
|---|---|
0 | Success |
2 | Usage error |
3 | A different telemetry backend is already configured; nothing was changed |
6 | The API could not be reached, or rejected the key |
7 | No 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.