Spec for the implementation of LLP 0245 (llp/0245-otel-attach-replaces-proxy.rfc.md, the design record). The GitHub issue is the tracker of record; the RFC stays the deliberation record and spawns its narrow decision LLPs on acceptance.
Problem Statement
Capturing Claude Code sessions today means routing all of Claude Code's HTTPS traffic through the local HypAware gateway proxy. That works, but the user pays standing costs that are intrinsic to TLS interception:
- Attach installs a machine-local CA into the login keychain (a security dialog at attach, and a
detach --purge cleanup duty later).
- The proxy env vars are pushed via launchd, which never reaches new windows of a terminal app that is already running. The user must fully quit and reopen the terminal, and HypAware cannot even detect whether they did.
- The daemon sits on the wire: if it is down or wedged, Claude Code itself is affected, not just the capture.
- The whole mechanism leans on proxy and CA behavior that Anthropic does not promise to keep.
Meanwhile Claude Code now ships a sanctioned export path for the same content: OTEL telemetry events plus raw API body files. HypAware does not use it yet.
Solution
hyp attach claude stops proxying and instead writes one reversible env block into ~/.claude/settings.json that turns on Claude Code's own telemetry. Events flow to a local OTLP listener owned by the @hypaware/claude plugin; raw request/response bodies land in a bounded spool directory that the listener projects and deletes. The same ai_gateway_messages rows land as today (OTEL becomes a third producer of the dataset, next to the live proxy and transcript backfill), and a new claude_telemetry_events dataset captures behavioral events the wire never showed (tool accept/reject decisions, permission mode changes, cost, hook and MCP health).
For the user: no keychain dialog, no proxy, no launchd env, no terminal restart. Attach is one settings write; detach removes exactly those keys and sweeps the spool. Remote Control keeps working because the base URL is never touched. The gateway proxy remains fully supported for every other client (codex, claude-desktop, openclaw, hermes, raw SDK traffic); this narrows the proxy's client list, it does not retire the gateway.
User Stories
- As a HypAware user, I want
hyp attach claude to work without a keychain security dialog, so that attaching feels safe and needs no trust decision.
- As a HypAware user, I want attach to take effect for every new Claude Code session without quitting and reopening my terminal app, so that capture starts immediately.
- As a HypAware user, I want Claude Code's API traffic to go directly to Anthropic rather than through the HypAware daemon, so that a down or wedged daemon can never break my Claude sessions.
- As a HypAware user, I want my captured sessions to land in
ai_gateway_messages with the same columns as before, so that all my existing queries, reports, and the context graph keep working unchanged.
- As a HypAware user, I want capture to include full prompt text, full response text, system prompt, tool definitions, and untruncated tool arguments, so that the OTEL path loses nothing the proxy path had.
- As a HypAware user, I want net-new behavioral data (tool accept/reject decisions with their source, permission mode changes, per-request USD cost, active time, lines of code, commit and PR counts, terminal type, hook executions, MCP server health, refusals) recorded in a queryable dataset, so that reports and the graph can use signals the wire never carried.
- As a HypAware user, I want
hyp query access to the new claude_telemetry_events dataset with typed columns for the hot fields, so that I can ask questions like "which tool calls did I reject this week" in plain SQL.
- As a HypAware user, I want steady-state disk growth at or below the proxy path's, so that switching capture modes never costs me storage.
- As a HypAware user, I want the transient body spool capped at a configurable byte budget with oldest-first eviction, so that a down daemon can never fill my disk.
- As a HypAware user, I want evicted or missed content recovered later by transcript backfill, so that best-effort delivery degrades to "captured later", never to "lost".
- As a privacy-conscious user, I want
.hypignore and the machine-local local-only list enforced inline at ingest, so that ignored projects never produce exported rows and the old fail-open window does not come back.
- As a privacy-conscious user, I want the bodies of ignored or policy-dropped sessions deleted from the spool (not merely skipped), so that opting out actually removes the content.
- As a privacy-conscious user, I want
/hypaware-ignore (per-session ignore) to keep working on the OTEL path, so that I can exclude one sensitive conversation on the spot.
- As a privacy-conscious user, I want
hyp purge and hyp detach claude to sweep the spool directory, so that no raw bodies outlive my decision to remove them.
- As a privacy-conscious user, I want the spool to live under the HypAware home with owner-only permissions, so that raw request bodies are never world-readable.
- As a Remote Control user, I want attach to leave
ANTHROPIC_BASE_URL and proxy settings alone, so that Remote Control works without any override keys.
- As a proxy-attached user, I want
hyp attach claude to migrate me: remove the proxy env keys, unwind the launchd env, offer detach --purge for the CA trust, and write the OTEL block, so that switching modes is one command.
- As a migrating user, I want sessions started before the flip (still proxying) and new OTEL sessions to dedupe into the same rows, so that the overlap window creates no duplicates.
- As a user on an old Claude Code, I want attach to refuse the OTEL switch, leave my existing attach untouched, and print an upgrade hint (
claude update), so that I am never silently downgraded to broken capture.
- As a HypAware user, I want a capture-health line in
hyp status comparing last event seen against last transcript activity, so that a silent capture gap is visible now instead of at report time.
- As a fleet operator, I want the same env block deliverable via managed settings, so that fleet machines attach without per-user setup.
- As a fleet operator, I want
claude_telemetry_events rows to carry the source signal needed for central forwarding, so that behavioral data reaches the server like message rows do.
- As a detaching user, I want detach to remove exactly the keys attach wrote and restore anything it displaced, so that my
settings.json ends up as if HypAware had never been there.
- As a HypAware developer, I want a hermetic smoke that drives the listener and spool end to end, so that regressions in the capture path are caught before release.
- As a release manager, I want a release-gate check that asserts the event and body shapes against the installed Claude Code, so that upstream flag or schema drift is caught on every release, not in production.
Implementation Decisions
-
Attach mode. The claude settings writer gains a third mode, otel, beside proxy and base_url. It reuses the existing self-describing _hypaware marker (managed env keys, managed hooks, atomic mtime-gated write, JSONC refusal, malformed-block backup) and the existing mode-switch key-release path. The core disk-driven detach replays the marker without knowing the keys, so detach needs no new adapter code.
-
Env block. Attach merges these keys into the settings env block (from the RFC, validated by the 2026-08-17 spike; inlined because the exact key set is the decision):
CLAUDE_CODE_ENABLE_TELEMETRY=1
OTEL_LOGS_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:<listener-port>
OTEL_LOG_USER_PROMPTS=1
OTEL_LOG_ASSISTANT_RESPONSES=1
OTEL_LOG_TOOL_DETAILS=1
OTEL_LOG_RAW_API_BODIES=file:<hyp-home>/spool/claude-bodies
Settings env overrides the shell environment for every launch path (terminal, desktop-spawned, SDK, background service), which is why no PATH shim or corporate launcher is needed.
-
Marker records the spool. The attach marker records the spool directory so detach and purge can sweep it.
-
Version floor. Below Claude Code 2.1.193 (event set; tool_source detail needs 2.1.214) attach REFUSES to switch modes: existing attach untouched, upgrade hint printed. There is no proxy fallback for the claude client; one attach mode per client keeps the test matrix single.
-
Listener source. A new listener source registered by @hypaware/claude through the kernel source registry, loopback-only, its own port. The OTLP http/json server machinery (routing, content-type and encoding handling, partialSuccess envelopes) is shared with @hypaware/otel rather than duplicated; the payload interpretation is claude-owned. The self-telemetry loop guard applies.
-
Events first, bodies for gaps. The event stream arrives pre-deduplicated (each content piece emitted once) and supplies identity, content, usage, and behavioral data. Body files supply what events lack: system text, the tools list, message ordering, untruncated tool args. A body file is projected and then deleted.
-
Projection unchanged. The listener yields the same projected-exchange values the live proxy and backfill providers yield today. Dataset, part_id dedupe, partitioning, and repo identity columns are untouched. OTEL is a third producer, not a new table.
-
New dataset. claude_telemetry_events, owned by @hypaware/claude (its first dataset: both a manifest contributes.datasets entry and a registration at activation). One row per event; typed columns for hot fields (event name, session id, tool name, decision, source, cost); an attributes JSON column for the rest. Not routed through @hypaware/otel's generic datasets, not widened into ai_gateway_messages. The registration sets the source signal so central forwarding works.
-
Spool policy. Under the HypAware home, owner-only permissions. Byte cap as a config value, default 512 MB, oldest-first eviction; evicted bodies are recovered by transcript backfill. Ingest DELETES (never merely skips) bodies of ignored or policy-dropped sessions. hyp purge and detach both sweep the spool.
-
Privacy seams inline. The .hypignore / machine-local-list check runs inline at ingest, with cwd known from the retained SessionStart hook, using the existing usage-policy drop sentinel. The late-drop (flush-time) machinery is not used on this path and the fail-open window must not reappear.
-
Session ignore transport (settled in this spec). The claude listener hosts the same session-ignore control route the gateway proxy hosts; hyp session ignore / unignore posts to both. Reuses the existing in-memory mechanism; no new on-disk contract.
-
Enrichment shrinks. Native message.uuid on events removes the identity race, so the flush-time settlement enricher is not used for OTEL-path rows. The SessionStart hook remains the source of cwd and git identity (events do not carry them; spike finding). Transcript backfill remains the recovery path.
-
Parent chains. parent_uuid, logical_parent_uuid, user_type, permission_mode stay in the schema and read null on the OTEL path; query_source and agent.name are the attribution source for sidechain/agent identity. No transcript join in live capture.
-
Capture health. The listener source's status details carry last-event-seen; hyp status renders a capture-health line comparing it to last transcript activity, with a diagnostics entry (and severity) when the gap exceeds a threshold. Status keeps answering from the status file only.
-
Migration surface. Attach on a proxy-attached machine: remove proxy env keys, unwind launchd env, offer detach --purge for CA trust, write the OTEL block. Both producers dedupe into the same rows during the overlap.
-
Glossary duty. CONTEXT.md's client-source gloss ("attaches the tool (rewrites its base URL)") is stale under both proxy and OTEL modes; update it in this change. The OTEL-attached claude remains a client source (autodetected, configured for you); the claude listener is claude-owned and is not an otel source in the glossary's sense.
Testing Decisions
A good test here exercises external behavior at the highest existing seam: content in at the listener's HTTP endpoint and the spool directory, rows out via hyp query sql, with the emitted spans as the internal proof the intended path ran. Tests never reach into projector internals or private state.
- Primary seam: hermetic smoke. A new smoke flow modeled on the existing gateway claude-capture flow: boot the real daemon in a temp HypAware home, POST OTLP/JSON events at the claude listener, drop body-file fixtures into the spool, drive the SessionStart hook, then assert
ai_gateway_messages rows (identity, cwd, git branch), claude_telemetry_events rows, body deletion after projection, and the capture spans. Prior art: the gateway claude-capture, otel listener-writes-rows, and hypignore capture-drop flows.
- Attach/detach smokes. Extend the attach-idempotency flow (attach twice, detach twice, attach after detach, unrelated user keys survive) and the attach/detach golden compare to the OTEL marker, including proxy-to-OTEL migration.
- Privacy smokes. An OTEL-path analog of the hypignore capture-drop flow: two sessions, one ignored; only the clean rows land, the drop signal fires, and the ignored session's bodies are deleted from the spool. Session-ignore via the control route gets the same treatment.
- Unit tests (root test suite, deterministic parts): settings writer OTEL mode, key release on mode switch, version-floor refusal, marker/undo round-trip (prior art: the existing claude settings attach tests); event-plus-body projection identity and part expansion (prior art: the projector identity tests); spool cap oldest-first eviction; capture-health line rendering and threshold diagnostics.
- Release gate. A shape-assertion smoke against the installed Claude Code (does the current release still emit the expected events, attributes, and body format) joins the release checklist per the RFC's flag-stability duty; it is a release/acceptance-tier check, not a PR gate.
Out of Scope
- Retiring the gateway proxy. It remains fully supported for codex, claude-desktop, openclaw, hermes, and raw SDK traffic.
- Reverting the proxy-default migration machinery (LLP 0242 to 0244); those docs get forward refs, not a revert.
- An events-only mode without body files (revisit if upstream event coverage closes the system-text/tools/args gaps).
- OTLP protobuf support on any listener (json-only stands).
- Fleet managed-settings delivery mechanics beyond documenting the env block.
- New reports or graph consumers of
claude_telemetry_events (the dataset ships; consumers come later).
- Upstream Claude Code bug fixes (e.g. the Remote Control absolute-form proxy issue, LLP 0246, which the OTEL path sidesteps for this client but does not fix).
Further Notes
- Design record: LLP 0245 (
llp/0245-otel-attach-replaces-proxy.rfc.md). Open questions 1, 2, 3, and 6 were resolved in the RFC on 2026-08-17; open question 4 (session-ignore transport) is settled by this spec (control route on the listener); open question 5's duty lands here as the capture-health line plus the release-gate shape smoke.
- Per the LLP house rules, acceptance of the RFC spawns narrow decision LLPs (injection mechanism, events-plus-bodies split, spool cap policy, settlement retirement scope, behavioral dataset shape) plus a spec LLP for the listener source; implementation code cites those decisions with
@ref annotations rather than restating rationale, and the ref-hygiene test enforces resolvable refs.
- Spike evidence (2026-08-17, Claude Code 2.1.233): full event set observed including undocumented hook events; every event carried session, prompt, user, org, terminal, and version identity; bodies held complete request JSON at about 145 KB per request; thinking text is redacted on both paths (signature kept), so neither path loses anything the other has.
- Storage reference points: the local all-history table is 182 MB; August is about 45,200 rows; request bodies repeat history each turn exactly as the proxied wire does, and the same part_id dedupe stores each part once.
Spec for the implementation of LLP 0245 (
llp/0245-otel-attach-replaces-proxy.rfc.md, the design record). The GitHub issue is the tracker of record; the RFC stays the deliberation record and spawns its narrow decision LLPs on acceptance.Problem Statement
Capturing Claude Code sessions today means routing all of Claude Code's HTTPS traffic through the local HypAware gateway proxy. That works, but the user pays standing costs that are intrinsic to TLS interception:
detach --purgecleanup duty later).Meanwhile Claude Code now ships a sanctioned export path for the same content: OTEL telemetry events plus raw API body files. HypAware does not use it yet.
Solution
hyp attach claudestops proxying and instead writes one reversibleenvblock into~/.claude/settings.jsonthat turns on Claude Code's own telemetry. Events flow to a local OTLP listener owned by the@hypaware/claudeplugin; raw request/response bodies land in a bounded spool directory that the listener projects and deletes. The sameai_gateway_messagesrows land as today (OTEL becomes a third producer of the dataset, next to the live proxy and transcript backfill), and a newclaude_telemetry_eventsdataset captures behavioral events the wire never showed (tool accept/reject decisions, permission mode changes, cost, hook and MCP health).For the user: no keychain dialog, no proxy, no launchd env, no terminal restart. Attach is one settings write; detach removes exactly those keys and sweeps the spool. Remote Control keeps working because the base URL is never touched. The gateway proxy remains fully supported for every other client (codex, claude-desktop, openclaw, hermes, raw SDK traffic); this narrows the proxy's client list, it does not retire the gateway.
User Stories
hyp attach claudeto work without a keychain security dialog, so that attaching feels safe and needs no trust decision.ai_gateway_messageswith the same columns as before, so that all my existing queries, reports, and the context graph keep working unchanged.hyp queryaccess to the newclaude_telemetry_eventsdataset with typed columns for the hot fields, so that I can ask questions like "which tool calls did I reject this week" in plain SQL..hypignoreand the machine-local local-only list enforced inline at ingest, so that ignored projects never produce exported rows and the old fail-open window does not come back./hypaware-ignore(per-session ignore) to keep working on the OTEL path, so that I can exclude one sensitive conversation on the spot.hyp purgeandhyp detach claudeto sweep the spool directory, so that no raw bodies outlive my decision to remove them.ANTHROPIC_BASE_URLand proxy settings alone, so that Remote Control works without any override keys.hyp attach claudeto migrate me: remove the proxy env keys, unwind the launchd env, offerdetach --purgefor the CA trust, and write the OTEL block, so that switching modes is one command.claude update), so that I am never silently downgraded to broken capture.hyp statuscomparing last event seen against last transcript activity, so that a silent capture gap is visible now instead of at report time.claude_telemetry_eventsrows to carry the source signal needed for central forwarding, so that behavioral data reaches the server like message rows do.settings.jsonends up as if HypAware had never been there.Implementation Decisions
Attach mode. The claude settings writer gains a third mode,
otel, besideproxyandbase_url. It reuses the existing self-describing_hypawaremarker (managed env keys, managed hooks, atomic mtime-gated write, JSONC refusal, malformed-block backup) and the existing mode-switch key-release path. The core disk-driven detach replays the marker without knowing the keys, so detach needs no new adapter code.Env block. Attach merges these keys into the settings
envblock (from the RFC, validated by the 2026-08-17 spike; inlined because the exact key set is the decision):Settings
envoverrides the shell environment for every launch path (terminal, desktop-spawned, SDK, background service), which is why no PATH shim or corporate launcher is needed.Marker records the spool. The attach marker records the spool directory so detach and purge can sweep it.
Version floor. Below Claude Code 2.1.193 (event set;
tool_sourcedetail needs 2.1.214) attach REFUSES to switch modes: existing attach untouched, upgrade hint printed. There is no proxy fallback for theclaudeclient; one attach mode per client keeps the test matrix single.Listener source. A new listener source registered by
@hypaware/claudethrough the kernel source registry, loopback-only, its own port. The OTLP http/json server machinery (routing, content-type and encoding handling, partialSuccess envelopes) is shared with@hypaware/otelrather than duplicated; the payload interpretation is claude-owned. The self-telemetry loop guard applies.Events first, bodies for gaps. The event stream arrives pre-deduplicated (each content piece emitted once) and supplies identity, content, usage, and behavioral data. Body files supply what events lack: system text, the tools list, message ordering, untruncated tool args. A body file is projected and then deleted.
Projection unchanged. The listener yields the same projected-exchange values the live proxy and backfill providers yield today. Dataset, part_id dedupe, partitioning, and repo identity columns are untouched. OTEL is a third producer, not a new table.
New dataset.
claude_telemetry_events, owned by@hypaware/claude(its first dataset: both a manifestcontributes.datasetsentry and a registration at activation). One row per event; typed columns for hot fields (event name, session id, tool name, decision, source, cost); an attributes JSON column for the rest. Not routed through@hypaware/otel's generic datasets, not widened intoai_gateway_messages. The registration sets the source signal so central forwarding works.Spool policy. Under the HypAware home, owner-only permissions. Byte cap as a config value, default 512 MB, oldest-first eviction; evicted bodies are recovered by transcript backfill. Ingest DELETES (never merely skips) bodies of ignored or policy-dropped sessions.
hyp purgeand detach both sweep the spool.Privacy seams inline. The
.hypignore/ machine-local-list check runs inline at ingest, with cwd known from the retained SessionStart hook, using the existing usage-policy drop sentinel. The late-drop (flush-time) machinery is not used on this path and the fail-open window must not reappear.Session ignore transport (settled in this spec). The claude listener hosts the same session-ignore control route the gateway proxy hosts;
hyp session ignore/unignoreposts to both. Reuses the existing in-memory mechanism; no new on-disk contract.Enrichment shrinks. Native
message.uuidon events removes the identity race, so the flush-time settlement enricher is not used for OTEL-path rows. The SessionStart hook remains the source of cwd and git identity (events do not carry them; spike finding). Transcript backfill remains the recovery path.Parent chains.
parent_uuid,logical_parent_uuid,user_type,permission_modestay in the schema and read null on the OTEL path;query_sourceandagent.nameare the attribution source for sidechain/agent identity. No transcript join in live capture.Capture health. The listener source's status details carry last-event-seen;
hyp statusrenders a capture-health line comparing it to last transcript activity, with a diagnostics entry (and severity) when the gap exceeds a threshold. Status keeps answering from the status file only.Migration surface. Attach on a proxy-attached machine: remove proxy env keys, unwind launchd env, offer
detach --purgefor CA trust, write the OTEL block. Both producers dedupe into the same rows during the overlap.Glossary duty. CONTEXT.md's client-source gloss ("attaches the tool (rewrites its base URL)") is stale under both proxy and OTEL modes; update it in this change. The OTEL-attached
clauderemains a client source (autodetected, configured for you); the claude listener is claude-owned and is not anotelsource in the glossary's sense.Testing Decisions
A good test here exercises external behavior at the highest existing seam: content in at the listener's HTTP endpoint and the spool directory, rows out via
hyp query sql, with the emitted spans as the internal proof the intended path ran. Tests never reach into projector internals or private state.ai_gateway_messagesrows (identity, cwd, git branch),claude_telemetry_eventsrows, body deletion after projection, and the capture spans. Prior art: the gateway claude-capture, otel listener-writes-rows, and hypignore capture-drop flows.Out of Scope
claude_telemetry_events(the dataset ships; consumers come later).Further Notes
llp/0245-otel-attach-replaces-proxy.rfc.md). Open questions 1, 2, 3, and 6 were resolved in the RFC on 2026-08-17; open question 4 (session-ignore transport) is settled by this spec (control route on the listener); open question 5's duty lands here as the capture-health line plus the release-gate shape smoke.@refannotations rather than restating rationale, and the ref-hygiene test enforces resolvable refs.