Skip to content

Commit b2da75e

Browse files
committed
Add Phase 5: telemetry opt-out via config and CI detection strategy
Adds plan for persistent telemetry disable in config.toml (top-level setting, not per-profile) with precedence: env var > config > default enabled. Includes CI/CD detection strategy that tags CI traffic as source="ci" rather than auto-disabling, preserving analytics visibility while enabling server-side filtering. https://claude.ai/code/session_01Y2eJZgKG78nDyN6Uw2tWQx
1 parent ca20e80 commit b2da75e

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

PLAN.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ Not in scope for this CLI PR, but documents the expected server-side changes:
295295
| `pkg/gateway/mcp/server.go` | Capture MCP client info, store on Server struct | Small |
296296
| `pkg/gateway/mcp/tools.go` | Add telemetry wrapping in tool dispatch | Medium |
297297
| `pkg/hookdeck/sdkclient.go` | No changes needed (already reads singleton) | None |
298+
| `pkg/config/config.go` | Add `TelemetryDisabled` field, read from viper in `constructConfig()` | Small |
299+
| `pkg/hookdeck/telemetry.go` | Update `telemetryOptedOut()` to accept config flag; add `detectSource()` for CI detection | Small |
300+
| `pkg/hookdeck/client.go` | Add `TelemetryDisabled` field, thread through opt-out check | Small |
298301

299302
## Risks and Edge Cases
300303

@@ -308,6 +311,125 @@ Not in scope for this CLI PR, but documents the expected server-side changes:
308311

309312
5. **Backward compatibility**: The server must handle both old (empty) and new telemetry payloads. Since it's JSON with new fields, old servers will simply ignore unknown keys. New servers should treat missing `source` as `"cli"` for backward compat.
310313

314+
## Phase 5: Telemetry Opt-Out via Config
315+
316+
### Problem
317+
318+
Telemetry opt-out is currently only possible via the `HOOKDECK_CLI_TELEMETRY_OPTOUT` environment variable. This requires users to set it in their shell profile or per-invocation, which is fragile and inconvenient. Users who want telemetry permanently disabled (corporate policy, personal preference) need a persistent config-based option.
319+
320+
### Design
321+
322+
Add a **top-level** `telemetry` setting to `config.toml`. No per-profile override initially — telemetry is a user-level concern, not a project-level one. We can always add profile granularity later if needed.
323+
324+
**Precedence (highest to lowest):**
325+
1. `HOOKDECK_CLI_TELEMETRY_OPTOUT` env var (existing, unchanged)
326+
2. `telemetry` in `config.toml` (new)
327+
3. Default: enabled
328+
329+
**Config format:**
330+
```toml
331+
# ~/.config/hookdeck/config.toml
332+
telemetry = false # disables telemetry globally
333+
profile = "default"
334+
335+
[default]
336+
api_key = "..."
337+
```
338+
339+
### Implementation
340+
341+
**File: `pkg/config/config.go`**
342+
343+
Add field to `Config` struct:
344+
```go
345+
type Config struct {
346+
// ... existing fields ...
347+
TelemetryDisabled bool
348+
}
349+
```
350+
351+
In `constructConfig()`, read the value:
352+
```go
353+
c.TelemetryDisabled = c.viper.GetBool("telemetry_disabled")
354+
```
355+
356+
**File: `pkg/hookdeck/telemetry.go`**
357+
358+
Update `telemetryOptedOut` to accept a config-based flag in addition to the env var:
359+
360+
```go
361+
func telemetryOptedOut(envVar string, configDisabled bool) bool {
362+
if configDisabled {
363+
return true
364+
}
365+
envVar = strings.ToLower(envVar)
366+
return envVar == "1" || envVar == "true"
367+
}
368+
```
369+
370+
**File: `pkg/hookdeck/client.go` and `pkg/hookdeck/sdkclient.go`**
371+
372+
Thread the config value through. The `Client` struct already receives config indirectly — the simplest approach is to add a `TelemetryDisabled bool` field to `Client` (set during construction in `Config.GetClient()`) and check it alongside the env var in `PerformRequest`.
373+
374+
**UX — setting the value:**
375+
376+
Users can edit `config.toml` directly, or we expose a command:
377+
```bash
378+
hookdeck config set telemetry_disabled true
379+
hookdeck config set telemetry_disabled false
380+
```
381+
382+
This depends on whether a generic `config set` command exists or is planned. If not, a simple `hookdeck telemetry off/on` subcommand is the alternative.
383+
384+
### CI/CD Environment Detection
385+
386+
**Context:** Some CLI tools (e.g., `npx @anthropic-ai/claude-code`) detect CI environments and disable telemetry by default. The question is whether hookdeck-cli should do the same.
387+
388+
**How CI detection works:** Check for well-known environment variables:
389+
- `CI=true` (GitHub Actions, GitLab CI, CircleCI, Travis, most CI systems)
390+
- `GITHUB_ACTIONS=true`
391+
- `GITLAB_CI=true`
392+
- `JENKINS_URL` is set
393+
- `CODEBUILD_BUILD_ID` is set (AWS CodeBuild)
394+
- `TF_BUILD=true` (Azure Pipelines)
395+
- `BUILDKITE=true`
396+
397+
A simple `isCI()` function checking `os.Getenv("CI")` covers ~90% of cases.
398+
399+
**Arguments for disabling in CI:**
400+
- CI runs can generate massive telemetry volume (every build, every PR, every retry)
401+
- CI telemetry is "noisy" — it represents automation, not human decision-making
402+
- CI environments often have strict data-exfiltration policies
403+
- Users don't expect background analytics from build tools
404+
405+
**Arguments against disabling in CI:**
406+
- Knowing that hookdeck-cli is used in CI/CD pipelines is genuinely useful product insight
407+
- CI usage patterns differ from interactive use — that's *interesting*, not noise
408+
- A GitHub Action (`hookdeck/hookdeck-cli-action`) is a deliberate integration — the user chose to put it in CI
409+
- Silently disabling telemetry means you lose visibility into a growing use case
410+
411+
**Recommended approach — middle ground:**
412+
413+
1. **Detect CI but don't fully disable.** Instead, set `source: "ci"` (alongside `"cli"` and `"mcp"`) so CI traffic can be filtered server-side. This gives the analytics team the ability to include or exclude CI data as needed, without losing it entirely.
414+
415+
2. **Respect explicit opt-out.** If `HOOKDECK_CLI_TELEMETRY_OPTOUT=1` or `telemetry_disabled = true` is set, disable completely — same as interactive mode.
416+
417+
3. **Do NOT auto-disable.** The user made a deliberate choice to run hookdeck-cli in CI. Unlike, say, a package manager that runs implicitly, hookdeck-cli is an explicit integration. Disabling telemetry by default in CI would be overly conservative.
418+
419+
4. **Log/document it.** If `CI=true` is detected, the telemetry header includes `"source": "ci"` and nothing else changes. Document this behavior so CI-conscious users know what's sent.
420+
421+
**Implementation:**
422+
```go
423+
func detectSource() string {
424+
if os.Getenv("CI") == "true" || os.Getenv("GITHUB_ACTIONS") == "true" {
425+
return "ci"
426+
}
427+
return "cli" // default; overridden to "mcp" by MCP server
428+
}
429+
```
430+
431+
This integrates cleanly with Phase 1's `source` field — it's just another source value. Server-side, PostHog dashboards can filter by `source = "ci"` to see CI-specific usage or exclude it from interactive metrics.
432+
311433
## Testing Strategy
312434

313435
1. **Unit tests for telemetry struct**: Verify JSON serialization includes new fields

0 commit comments

Comments
 (0)