Skip to content

feat(claude): v0.2.0-beta — transcript tracing, per-turn traces, config file, JSONL logging#40

Open
fangxiu-wf wants to merge 23 commits into
alibaba:mainfrom
fangxiu-wf:feat/per-turn-traces-and-step-reasoning
Open

feat(claude): v0.2.0-beta — transcript tracing, per-turn traces, config file, JSONL logging#40
fangxiu-wf wants to merge 23 commits into
alibaba:mainfrom
fangxiu-wf:feat/per-turn-traces-and-step-reasoning

Conversation

@fangxiu-wf

@fangxiu-wf fangxiu-wf commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

v0.2.0-beta 核心变更

Transcript-based Tracing(新数据源)

新增 transcript.js,解析 Claude Code 原生 session transcript JSONL,替代对 intercept.js HTTP 拦截的依赖:

  • 直接读取 ~/.claude/projects/<hash>/<session-id>.jsonl,提取每次 LLM 调用的模型、token 用量、输入输出消息
  • 支持流式分块合并和 content block 去重(按 message.id 分组)
  • 兼容所有 Claude Code 版本(不依赖特定 HTTP 客户端实现)
  • 当 transcript 不可用时,自动回退到 intercept.js HTTP 拦截

O(N²) OOM 修复

transcript.jsinput_messages 改为 delta 模式存储(_input_is_delta: true),避免每次 LLM call 累积拷贝完整历史:

  • 35MB transcript:内存从 >4GB 降至 ~98MB
  • log 生成端适配增量 hash 计算

Per-turn 独立 Trace

每轮对话生成独立 trace(新 traceId),同一 session 通过 gen_ai.session.id 关联。Span 层级遵循 ENTRY → AGENT → STEP → TOOL/LLM 标准结构。

GenAI 语义规范对齐

使用 @loongsuite/opentelemetry-util-genai SDK 的 ExtendedTelemetryHandler 重构 span 创建流程,属性名、层级结构、消息格式完全对齐 ARMS GenAI 语义规范。

配置文件支持

新增 ~/.claude/otel-config.json,优先级:配置文件 > 环境变量 > 默认值。支持 endpoint、headers、service_name、debug、log_enabled 等 9 项配置。

JSONL 日志采集

可选的本地日志功能,遵循 AI Agent EventSchema(event_t)规范:

  • 事件拆分:llm.request + llm.responsetool.call + tool.result
  • Chain hash 增量校验 + 每日文件轮转
  • 支持纯日志模式(Log-only),跳过 OTel Trace 导出

Alias 环境变量精简

GenAI SDK 环境变量(Group A)从 alias 移至 bin/otel-claude-hook 入口点程序化设置;NODE_OPTIONS + intercept.js 从 alias 中移除。Legacy alias 自动升级清理。

消息格式转换

新增 message-converter.js,支持 Anthropic、OpenAI Chat、OpenAI Responses 三种协议自动转换为 ARMS JSON Schema 格式。含多模态图片支持(BlobPart / UriPart)。

Pilot 集成

  • --no-alias install 选项:跳过 shell alias 设置,用于 pilot 等托管安装
  • setup-alias.sh --minimal 模式:仅清理旧 alias,不写入新 alias

Cursor IDE 兼容

  • hook 配置增加 matcher 字段
  • 自动检测 cursor_version 并跳过处理,避免与 Cursor 自身 hooks 冲突

Bug 修复

  • 修复 -p 非交互模式下 LLM/STEP span 丢失(intercept.js setImmediate 竞态)
  • 修复 OTEL_RESOURCE_ATTRIBUTES 未完整解析到 Resource
  • 修复 input.messages_delta 误发送累积历史而非增量
  • 孤立 pre_tool_use 事件生成带 tool.orphaned=true 的 TOOL span
  • cmdStop() 导出后清空事件,防止重复 trace
  • package-lock.json 修复 link: true 导致新用户 MODULE_NOT_FOUND
  • hook-entry.sh 使用相对路径优先 + 绝对路径兜底 + 文件不存在时 exit 0,修复远程开发机 MODULE_NOT_FOUND
  • setup-alias.sh / remote-install.sh 追加内容前添加尾部换行保护,修复卸载时吞掉用户 .zshrc 最后一行
  • Issue 6: 修复交互式会话中 transcript 数据重复(每个 turn 的 Stop hook 重复解析全量 transcript)。新增 byteOffset 增量读取,每次 Stop 只解析新增数据
  • Issue 7: 修复 PostToolUse 丢失时 tool.call 排在最后一个 llm.response 之后的顺序错误。alignWithHookEvents 新增 prevEnd+0.001 下界保证正确排序
  • Issue 8: input_tokens 偏小 — Anthropic API 的 usage.input_tokens 只报告 cache 断点后的 token 数,修复为 input_tokens + cache_read + cache_creation 全量值(7 处)
  • Issue 9: 历史 transcript 锚点抢占 — 中途安装插件时历史 LLM 记录抢占当前 turn 的 hook 锚点,修复为只对最后 N 条事件配对
  • Issue 10: logOnly 模式 trace_id/span_id 缺失 — 所有模式下 JSONL 记录现在都包含 trace_id (32-hex) / span_id (16-hex) / parent_span_id (16-hex)
  • Issue 11: CLAUDE_CONFIG_DIR 环境变量兼容 — Harbor/CI 场景下自动适配 settings.json 和 otel-config.json 路径

Changed Files

文件 变更 说明
src/cli.js Modified SDK 重构 span 创建、per-turn trace、transcript 集成、JSONL 日志、event_t、--no-alias、Cursor 兼容、hook-entry.sh 相对路径增量 transcript offset 持久化input_token 全量计算span_id 增强CLAUDE_CONFIG_DIR 兼容
src/transcript.js Modified Claude Code 原生 transcript JSONL 解析、流式合并、delta 模式、byteOffset 增量读取PostToolUse 丢失时排序修复锚点抢占防护 (_discarded)
src/config.js Modified 配置文件加载(config file > env var > default)、CLAUDE_CONFIG_DIR 路径适配
src/logger.js New JSONL 日志采集、chain hash、文件轮转
src/message-converter.js New LLM 消息格式转换(3 协议 → ARMS JSON Schema)
src/hooks.js Modified 新增 extractToolResult / extractToolError
src/intercept.js Modified 修复 setImmediate 竞态
src/telemetry.js Modified 完整解析 OTEL_RESOURCE_ATTRIBUTES
bin/otel-claude-hook Modified GenAI SDK env var 注入、--no-alias option、--settings-path 选项
scripts/setup-alias.sh Modified --minimal 模式、legacy alias 升级、尾部换行保护
scripts/remote-install.sh Modified 尾部换行保护
package.json Modified 版本 0.2.0-beta,新增 genai-util SDK 依赖
test/*.test.js Modified 292 tests 全部通过,新增 transcript 增量读取 + PostToolUse 丢失排序测试
test/e2e-verify-fixes.js New E2E 验证脚本(input_token / anchor_steal / span_id 三项修复)
README.md Modified transcript 文档、alias 更新、CLI 参考
CHANGELOG.md Modified 0.2.0-beta 变更日志

Test plan

  • npm test — 292 tests 全部通过(9 suites, 0 failures)
  • Transcript 解析:单轮/多轮/流式合并/delta 模式全覆盖
  • OOM 修复验证:35MB transcript 不再触发 heap 溢出
  • Pilot 安装流程:pack.shpackage.sh--package-url file:// 完整验证
  • 端到端 ARMS 验证:7 spans,ENTRY → AGENT → STEP → LLM/TOOL 层级正确
  • 本地 JSONL 日志:event_t schema 格式正确
  • Cursor IDE:hook 带 matcher 字段,Cursor 调用时自动跳过
  • hook-entry.sh 相对路径:bin 不存在时 exit 0 不阻塞 Claude Code
  • 尾部换行保护:无换行文件追加+卸载后用户最后一行不丢失
  • Issue 6 增量 transcript:3 轮交互会话每轮只产生当轮 LLM 数据,不再重复
  • Issue 7 PostToolUse 丢失:tool.call 正确排在对应 llm.response 之后而非末尾
  • Issue 8 input_token:E2E 验证 usage.input_tokens = 18200(含 cache),而非 API 原始值 200
  • Issue 9 锚点抢占:5 条 transcript LLM 记录中 3 条历史被 _discarded,只输出当前 turn 的 2 条
  • Issue 10 span_id:全部 JSONL 记录都有合法 32位 trace_id + 16位 span_id/parent_span_id
  • Issue 11 CLAUDE_CONFIG_DIR:Harbor 场景下 settings.json 和 otel-config.json 正确写入 $CLAUDE_CONFIG_DIR/

🤖 Generated with Claude Code

fangxiu-wf and others added 17 commits April 24, 2026 17:02
- Each conversation turn now produces an independent trace (new traceId),
  instead of cumulative session-wide traces. All turns share gen_ai.session.id.
- STEP spans now map to LLM reasoning cycles (one llm_call + resulting tool
  calls), not conversation turn boundaries, matching ARMS semantic conventions.
- gen_ai.session.id is set on ALL spans (ENTRY, AGENT, STEP, LLM, TOOL),
  not just ENTRY.
- Orphaned pre_tool_use events (where Claude Code drops PostToolUse hooks)
  now produce TOOL spans with tool.orphaned=true, preventing missing tools
  in traces.
- Events are cleared after successful export in cmdStop(), fixing duplicate
  trace generation when Stop hook fires per-turn.
- Add message-converter module for Anthropic/OpenAI/Responses protocol
  conversion to ARMS semantic format.
- Update README trace hierarchy diagram and export flow description.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The lock file previously contained "link": true pointing to the local
monorepo sibling directory, causing fresh clones to symlink instead of
downloading from npm. New users got MODULE_NOT_FOUND because the local
opentelemetry-util-genai had no dist/cjs/ build output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Support image content blocks across all three LLM API protocols
(Anthropic, OpenAI Chat, OpenAI Responses) by converting them to
ARMS semantic convention BlobPart (base64) and UriPart (URL) formats.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ~/.claude/otel-config.json support (priority: config file > env var > default)
- Add JSONL log collection with chain hash and daily file rotation
- Fix -p mode missing LLM/STEP spans (remove setImmediate race in intercept.js)
- Fix OTEL_RESOURCE_ATTRIBUTES not fully parsed into Resource
- Update README with config file docs, new modules, and JSONL logging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t + update OSS paths

- Add logOnly branch in exportSessionTrace() to skip OTel span building when only JSONL logging is needed
- Add log_filename_format config option ("default" vs "hook") for BaseHookInput compatibility
- Update OSS paths from agenttrack to opentelemetry-instrumentation-claude
- Fix test flakiness caused by local otel-config.json interfering with env-var-based tests
- Document log-only mode in README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Align generateTurnLogRecords() output with the unified AI Agent EventSchema:
- Split combined llm_call records into separate llm.request + llm.response events
- Split combined tool records into separate tool.call + tool.result events
- Rename all gen_ai.* fields to dotted namespace (event.name, session.id, etc.)
- Add event.id (UUID), user.id, agent.type, agent.name to every record
- Add usage.total_tokens, tool.result.status, tool.result.duration_ms
- Add is_error/error.type/error.message on tool.result for failed tools

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update log field references from gen_ai.* to event_t dotted namespace
(event.name, session.id, message.role, etc.) and document the split
event model (llm.request/llm.response, tool.call/tool.result).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…laude-code"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- setup-alias.sh: add file writability check, handle cat >> failure gracefully
- uninstall.sh: add npm uninstall -g otel-claude-hook step for global package cleanup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…xecution

Same fix as codex plugin: generate hook-entry.sh wrapper at
~/.cache/opentelemetry.instrumentation.claude/ with built-in Node
discovery (nvm, homebrew, volta, fnm fallbacks). Register absolute
path in settings.json instead of bare `otel-claude-hook` command.

- Add generateHookEntry(), packageBinPath(), hookEntryCacheDir()
- Add isOtelHookCommand() to match both old and new formats
- Extract removeOtelHooksFromSettings() for shared cleanup logic
- Modify installIntoSettings() to clean old entries before adding new
- Modify uninstallFromSettings() to use isOtelHookCommand()
- Update install.sh to use `node "$PKG_DIR/bin/..."` absolute path
- Update uninstall.sh cleanup to match hook-entry.sh format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ility

Cursor's third-party hooks parser requires the `matcher` field on each
hook entry. Missing it causes "Failed to parse claude-user Claude hooks
configuration" error. Add `"matcher": "*"` to all registered hook events.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
input.messages_delta was recording the full cumulative API context instead
of only the new messages added since the last LLM call. Track previous
input messages and use slice-based diff to produce correct deltas.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cursor reads ~/.claude/settings.json hooks and executes them, causing
duplicate data collection alongside Cursor's own hooks. Detect Cursor
by checking for cursor_version in the stdin payload and return early.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…M fix

- Add transcript.js: parse Claude Code native transcript JSONL for LLM call data
  (replaces intercept.js HTTP interception, works with all Claude Code versions)
- Inject GenAI SDK env vars (Group A) in bin/otel-claude-hook entry point,
  removing them from alias (plugin code handles defaults programmatically)
- setup-alias.sh: remove Group A vars + NODE_OPTIONS + intercept.js from alias;
  add --minimal mode (cleanup only, no alias write) for pilot installations
- Add upgrade_old_alias() to auto-clean legacy alias blocks containing intercept.js
- Fix O(N²) OOM in transcript parsing: store input_messages as delta instead of
  cumulative copies (35MB transcript: memory from >4GB to ~98MB)
- Log generation: incremental hash computation for delta mode events
- Add maybeSaveTranscriptPath() + enrichChildStateWithTranscript() for subagent support
- Add comprehensive tests for transcript parsing and new functions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allows managed installations (like pilot) to skip shell alias setup
during plugin install, preventing redundant alias write + cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e, and config isolation

- cli.test: pass entryPath to installIntoSettings so hook dedup works correctly
- cli.test: remove agent.name assertion (field was removed in 8f5c00f)
- transcript.test: expect delta input_messages with _input_is_delta flag
- logger.test: mock otel-config.json to prevent local pilot config from interfering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fangxiu-wf fangxiu-wf changed the title feat(claude): per-turn traces, config file, JSONL logging, and bug fixes feat(claude): v0.2.0-beta — transcript tracing, per-turn traces, config file, JSONL logging May 8, 2026
fangxiu-wf and others added 6 commits May 8, 2026 16:15
- Version: 0.1.0 → 0.2.0-beta
- CHANGELOG: add 0.2.0-beta section with all PR alibaba#40 changes
- README: document transcript-based tracing, simplified alias,
  --no-alias option, Cursor compatibility, project structure update

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… newline protection

- generateHookEntry() now uses $(dirname "$0") relative path first, with
  install-time absolute path as fallback, and graceful exit 0 when bin is
  missing (fixes MODULE_NOT_FOUND on remote dev machines)
- setup-alias.sh and remote-install.sh: add trailing newline protection
  before cat >> to prevent uninstall from eating the user's last line

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ring

Issue 6: Fix transcript data duplication in interactive sessions by adding
byte-offset-based incremental reading to parseClaudeTranscript(). Each
Stop hook now reads only new transcript data since the last call.

Issue 7: Fix tool.call ordering when PostToolUse hook is dropped (~30%
drop rate). alignWithHookEvents now uses prevEnd+0.001 as lower bound
for request_start_time, ensuring correct sort order.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Identifies traces from this plugin as originating from Claude Code in
ARMS. Always set, not overridable via OTEL_RESOURCE_ATTRIBUTES, so
mixed-agent dashboards can reliably filter by agent system.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, CLAUDE_CONFIG_DIR compat

1. input_tokens now includes cache_read + cache_creation tokens (7 locations)
2. alignWithHookEvents pairs only last N events to prevent historical anchor steal
3. JSONL records now include trace_id/span_id/parent_span_id in all modes
4. Respect CLAUDE_CONFIG_DIR env var for Harbor/CI scenarios (settings + config paths)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant