feat(core): Add ARMS session user ID - #7921
Conversation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
E2E test reportBaseline
Local bundle
ARMS online smoke
Additional verification
|
|
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Thanks for the PR, @doudouOUC — this is a clean continuation of the ARMS telemetry alignment work you shipped in #7536 and #7635.
Template ✓ — all required sections present, bilingual, well-structured.
Problem: real and demonstrated. ARMS Session Analysis reads gen_ai.user.id from span-level attributes, and there is currently no way to populate it. The ARMS smoke test with arms-smoke-20260728-1628 shows the value landing on LLM, Tool, Session, and User views. This is not theoretical.
Direction: aligned with the established ARMS alignment trajectory visible in the CHANGELOG (#7536, #7635). The feature is opt-in, does not change default behavior, and the PR explicitly scopes out shared-daemon identity. That said, this PR touches telemetry configuration and span propagation — a policy escalation area. Escalating to a maintainer for direction sign-off before proceeding with full code review.
Size: 98 production logic lines (config resolution + span attribute propagation), 410 test lines, 65 docs lines, 4 schema lines. Well within bounds.
Approach: the scope feels right. Configuration resolution follows the existing resolveTelemetrySettings pattern (env precedence, trim, blank fallback). The interactionIdentityByPromptId map is a reasonable way to carry identity across the interaction→tool-result gap without keeping ended spans alive. Anti-spoofing (tool attributes cannot override the canonical user ID) and TTL expiry are good touches. No drive-by changes or scope creep.
Risk: packages/cli/src/acp-integration/session/Session.ts matches a high-risk path pattern (acp-integration). The change there is minimal (one line — passing promptId to startToolSpan), but flagging it for reviewer awareness.
@doudouOUC — this is a policy gate, not a quality rejection. The PR looks well-built; it just needs a maintainer to confirm the telemetry direction before we proceed.
@wenshao — could you confirm the ARMS session user ID direction? This adds telemetry.userId / QWEN_TELEMETRY_USER_ID → gen_ai.user.id on interaction, LLM, Tool, and Agent spans. Opt-in, process-wide, no Resource/Baggage leakage.
中文说明
感谢 @doudouOUC 的 PR——这是你在 #7536 和 #7635 中已落地的 ARMS 遥测对齐工作的干净延续。
模板 ✓——所有必填段落齐全,双语,结构清晰。
问题: 真实且已验证。ARMS 会话分析从 Span 级属性读取 gen_ai.user.id,目前没有途径填充该字段。使用 arms-smoke-20260728-1628 的 ARMS 在线 smoke 验证显示该值已出现在 LLM、Tool、Session 和 User 视图。这不是理论性问题。
方向: 与 CHANGELOG 中可见的 ARMS 对齐轨迹一致(#7536、#7635)。该功能为按需启用,不改变默认行为,PR 也明确排除了共享 daemon 身份。但本 PR 触及了遥测配置和 Span 传播——属于策略升级区域。在进行完整代码审查之前,先转交维护者确认方向。
规模: 98 行生产逻辑(配置解析 + Span 属性传播),410 行测试,65 行文档,4 行 schema。在合理范围内。
方案: 范围合理。配置解析遵循现有 resolveTelemetrySettings 模式(环境变量优先、trim、空白回退)。interactionIdentityByPromptId Map 是在 interaction→tool-result 间隙传递身份的合理方式,无需保持已结束 Span 的存活。防伪造(Tool 属性无法覆盖规范用户 ID)和 TTL 过期是好的设计。无顺手改动或范围蔓延。
风险: packages/cli/src/acp-integration/session/Session.ts 匹配高风险路径模式(acp-integration)。该处改动极小(一行——将 promptId 传递给 startToolSpan),但标记以供审阅者注意。
@doudouOUC——这是策略门控,不是质量否定。PR 看起来构建良好;只是需要维护者确认遥测方向后再继续。
@wenshao——能否确认 ARMS 会话用户 ID 的方向?本 PR 添加 telemetry.userId / QWEN_TELEMETRY_USER_ID → 在 interaction、LLM、Tool 和 Agent Span 上写入 gen_ai.user.id。按需启用,进程级,无 Resource/Baggage 泄漏。
— Qwen Code · qwen3.8-max-preview
|
@qwen-code /takeover |
|
🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round — no code changeThe only feedback newer than the last evaluation is a This open item is a product/scope decision, not a code defect, so it is not
No action is required from the author beyond awaiting the maintainer's 中文说明Autofix 审查轮次——无代码改动本次评估之后唯一的反馈是来自自动审查机器人的一条 该未决项是一个产品/范围决策,而非代码缺陷,因此不应由我来定夺。按照审查流程,我将该
作者无需采取任何行动,只需等待维护者的方向确认。 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
Local build & verification report (maintainer-side, merge reference)Independently verified head Verdict: behavior verified end-to-end, causally attributable to this PR, tests bite, no leaks found. Merge-ready from the verification standpoint (direction sign-off itself is the pending policy gate from the triage bot; this report is the reference for it). 1. PR test suites replicated
2. Do the new tests actually bite? (mutation checks)
Both mutations were verified to have actually landed (grep count) before trusting the red run. 3. Maintainer E2E scenarios (not in the PR — real bundled CLI, full LLM→Tool→LLM turns)These cover paths the PR itself only proves at unit level:
Span-level evidence for S1: interaction, both LLM spans and the Tool span carry the identical id; the second LLM span is the tool-result continuation ( 4. Causal A/B — is this PR the cause?Same tree, same tests, only 5. Observations (non-blocking)
Not verified hereARMS online rendering (covered by the author's 中文版本本地构建与验证报告(维护者侧,合并参考)在隔离 worktree 中独立验证了 head 结论:行为端到端验证通过,因果上可归因于本 PR,测试确实"咬人",未发现泄漏。从验证角度看已具备合并条件(方向确认本身是 triage bot 挂起的策略门,本报告即为其参考依据)。 1. PR 测试套件复跑
2. 新测试是否真的有效?(变异检验)
两个变异均先以 grep 计数确认真实落地后才采信红色结果。 3. 维护者侧 E2E 场景(非 PR 内容——真实打包 CLI,完整 LLM→Tool→LLM 流程)覆盖 PR 仅在单元层面证明的路径:
S1 的 Span 级证据:interaction、两个 LLM Span 和 Tool Span 携带完全相同的 id;第二个 LLM Span 是 ToolResult 延续( 4. 因果 A/B——行为确实由本 PR 引入吗?同一棵树、同一测试,只交换 5. 观察项(不阻塞合并)
本次未验证ARMS 在线渲染(作者的 |
|
@qwen-code /triage |
|
Thanks for the PR, @doudouOUC — this is a clean continuation of the ARMS telemetry alignment work from #7536 and #7635. Template looks good ✓ Problem: real and demonstrated. ARMS Session Analysis reads Direction: aligned with the established ARMS alignment trajectory. The feature is opt-in, does not change default behavior, and the PR explicitly scopes out shared-daemon identity. The previous run escalated this for telemetry direction review; @wenshao has since approved, confirming the direction. CHANGELOG has no direct Size: 163 production logic lines (config resolution + span attribute propagation + docs), 410 test lines, 4 schema lines. Core paths touched ( Approach: the scope feels right. Configuration resolution follows the existing Risk: Moving on to code review. 🔍 中文说明感谢 @doudouOUC 的 PR——这是 #7536 和 #7635 中 ARMS 遥测对齐工作的干净延续。 模板完整 ✓ 问题:真实且已验证。ARMS 会话分析从 Span 级属性读取 方向:与已建立的 ARMS 对齐轨迹一致。该功能为按需启用,不改变默认行为,PR 也明确排除了共享 daemon 身份。上一次运行因遥测方向升级而暂停;@wenshao 已批准,确认了方向。CHANGELOG 中没有直接的 规模:163 行生产逻辑(配置解析 + Span 属性传播 + 文档),410 行测试,4 行 schema。触及核心路径( 方案:范围合理。配置解析遵循现有 风险: 进入代码审查 🔍 — Qwen Code · qwen3.8-max-preview Reviewed at |
Code reviewIndependent proposal: given the problem (ARMS reads Comparison: the PR matches this approach almost exactly. The implementation is clean and follows existing patterns throughout. No correctness bugs, security holes, or regressions found. Specific observations:
No blockers. No AGENTS.md violations. sequenceDiagram
participant Config as resolveTelemetrySettings
participant Interaction as startInteractionSpan
participant Map as interactionIdentityByPromptId
participant LLM as startLLMRequestSpan
participant Tool as startToolSpan
participant Agent as startSubagentSpan
Config->>Config: env QWEN_TELEMETRY_USER_ID ?? settings.userId (trim, blank→undefined)
Interaction->>Interaction: gen_ai.user.id on span attributes
Interaction->>Map: store {startTime, gen_ai.user.id} by promptId
LLM->>LLM: resolve from parent context
Tool->>Map: continuation turn: resolve by promptId
Tool->>Tool: gen_ai.user.id (after ...attrs, anti-spoof)
Agent->>Agent: resolve from subagent/tool/interaction context chain
Note over Map: swept by existing 30-min TTL
Files changed (18 total)
TestingCI evidence (fetched via API for
Ubuntu unit tests pass. macOS, Windows, and integration tests are skipped — expected for a fork PR (no secrets access). No failures. Real-scenario testing: N/A — no TUI change. The PR adds a telemetry span attribute; the author's ARMS smoke test ( 中文说明代码审查独立方案: 给定问题(ARMS 从 Span 级属性读取 对比: PR 与此方案几乎完全一致。实现干净,全程遵循现有模式。未发现正确性 bug、安全漏洞或回归。
无阻塞问题。无 AGENTS.md 违规。 测试CI 证据:Ubuntu 单元测试通过。macOS、Windows 和集成测试跳过——fork PR 预期行为。无失败。 真实场景测试:N/A——无 TUI 变更。作者的 ARMS smoke 测试是其自身声明,未在此独立验证。沙箱化的 — Qwen Code · qwen3.8-max-preview Reviewed at |
|
Confidence: 4/5 — solid implementation that follows existing patterns, comprehensive tests, clean scope; the only gap is that macOS/Windows/integration CI is skipped (fork PR) and the ARMS smoke is the author's claim. Stepping back: this PR does one thing well. It adds an opt-in span-level identity for ARMS Session Analysis, following the exact same config resolution, span propagation, and TTL patterns already established in the codebase. The independent proposal I wrote before reading the diff matches what the author built — no simpler path was missed. The code is straightforward. The test coverage is thorough: config resolution with precedence and blank fallback, propagation across all span types, anti-spoofing, TTL expiry, concurrent scoped interaction isolation, subagent inheritance (all three invocation kinds), continuation-turn tool-result resolution, and Baggage non-leakage. The integration test adds a "not configured → omitted" case that guards against accidental always-on behavior. The previous run escalated for telemetry direction review; @wenshao approved, confirming the direction. The high-risk path match ( Non-blocking note: the Approving. ✅ 中文说明置信度:4/5——实现扎实,遵循现有模式,测试全面,范围干净;唯一的缺口是 macOS/Windows/集成 CI 跳过(fork PR),ARMS smoke 是作者声明。 退一步看:这个 PR 做好了一件事。它为 ARMS 会话分析添加了按需启用的 Span 级身份,完全遵循代码库中已建立的配置解析、Span 传播和 TTL 模式。我在阅读 diff 之前写的独立方案与作者构建的一致——没有遗漏更简路径。 代码直截了当。 测试覆盖全面:配置解析(优先级和空白回退)、所有 Span 类型的传播、防伪造、TTL 过期、并发作用域交互隔离、子 Agent 继承(三种调用类型)、延续轮 tool-result 解析、Baggage 非泄漏。集成测试添加了"未配置→省略"用例,防止意外的始终启用行为。 上一次运行因遥测方向升级而暂停;@wenshao 已批准,确认了方向。高风险路径匹配( 非阻塞备注: 批准 ✅ — Qwen Code · qwen3.8-max-preview Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
wenshao
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — "Integration Tests (CLI, No Sandbox)" was skipped in CI and its suite (integration-tests/cli/gen-ai-telemetry.test.ts) was not run locally.
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — "Integration Tests (CLI, No Sandbox)" was skipped in CI and its suite (integration-tests/cli/gen-ai-telemetry.test.ts) was not run locally。
— qwen3.8-max-preview via Qwen Code /review
| attrs?: Record<string, string | number | boolean>, | ||
| description?: string, | ||
| promptId?: string, |
There was a problem hiding this comment.
[Suggestion] The new promptId is a 4th positional optional string, sitting immediately after the also-optional string description. Two adjacent optional string parameters with different meanings are a silent-swap footgun: a future call site written as startToolSpan(name, attrs, promptId) compiles cleanly (both slots are string | undefined), lands the value in description (emitted as a bogus gen_ai.tool.description), and leaves promptId undefined. On a tool-result continuation turn — where the interaction span has ended and no live ALS context exists — resolveGenAiUserId(undefined, undefined) returns undefined, so gen_ai.user.id is silently dropped. Because startSubagentSpan has no promptId fallback, an Agent tool launched from that span loses the user ID across its whole LLM/tool/agent subtree. All three current call sites pass the argument correctly, so this is hardening rather than a live bug. — Failure scenario: a future refactor mis-passes promptId → silent ARMS attribution gaps with no compile error and no failing test.
Suggested fix (spans the signature plus its three call sites, so not a one-click suggestion): collapse the trailing optionals into an options object:
export function startToolSpan(
toolName: string,
attrs?: Record<string, string | number | boolean>,
opts?: { description?: string; promptId?: string },
): Span {中文说明
新增的 promptId 是第 4 个位置可选 string 参数,紧跟在同样是可选 string 的 description 之后。两个相邻、含义不同的可选 string 参数构成一个“静默交换”陷阱:未来某个调用点若写成 startToolSpan(name, attrs, promptId),TypeScript 会正常编译(两个位置都是 string | undefined),该值会落入 description(被写成伪造的 gen_ai.tool.description),而 promptId 保持 undefined。在 tool-result 延续轮次中(此时 interaction span 已结束,没有存活的 ALS 上下文),resolveGenAiUserId(undefined, undefined) 返回 undefined,于是 gen_ai.user.id 被静默丢弃。由于 startSubagentSpan 没有 promptId 回退,从该 span 启动的 Agent tool 会在其整个 LLM/tool/agent 子树中丢失用户 ID。当前三个调用点都正确传参,因此这是加固性建议,而非现存 bug。—— 失败场景:未来重构误传 promptId → ARMS 归属出现静默缺口,且没有编译错误、没有失败的测试。
建议修复(涉及签名及其三个调用点,故非一键 suggestion):将尾部可选参数收敛为一个 options 对象。
— qwen3.8-max-preview via Qwen Code /review
| const canonicalSpanNames = new Set([ | ||
| 'qwen-code.interaction', | ||
| 'qwen-code.llm_request', | ||
| 'qwen-code.tool', | ||
| ]); |
There was a problem hiding this comment.
[Suggestion] This allowlist omits qwen-code.subagent, so the negative-assertion loop below it (if (!record.name || !canonicalSpanNames.has(record.name)) expect(attributes).not.toHaveProperty('gen_ai.user.id')) encodes the invariant “only interaction/llm_request/tool spans may carry gen_ai.user.id”. That contradicts what this same PR establishes elsewhere: docs/developers/development/telemetry.md lists the subagent span as carrying the optional gen_ai.user.id, the unit tests assert all three invocation kinds (foreground/fork/background) inherit it, and startSubagentSpan (session-tracing.ts) adds it. Today the scenario emits no subagent span, so the assertion never fires; but if the scenario is ever extended to invoke a subagent, this assertion fails on correct code — steering a developer to remove gen_ai.user.id from subagent spans to go green, regressing the behavior the unit tests guard. — Failure scenario: extend the integration scenario to invoke an Agent → the negative assertion fails on correct code → a developer removes the attribute to fix the test.
| const canonicalSpanNames = new Set([ | |
| 'qwen-code.interaction', | |
| 'qwen-code.llm_request', | |
| 'qwen-code.tool', | |
| ]); | |
| const canonicalSpanNames = new Set([ | |
| 'qwen-code.interaction', | |
| 'qwen-code.llm_request', | |
| 'qwen-code.tool', | |
| 'qwen-code.subagent', | |
| ]); |
中文说明
该允许列表遗漏了 qwen-code.subagent,因此下方的负向断言循环(if (!record.name || !canonicalSpanNames.has(record.name)) expect(attributes).not.toHaveProperty('gen_ai.user.id'))编码了“只有 interaction/llm_request/tool span 可以携带 gen_ai.user.id”这一不变量。这与本 PR 在其他地方确立的契约相矛盾:docs/developers/development/telemetry.md 将 subagent span 列为携带可选 gen_ai.user.id,单元测试断言三种调用方式(foreground/fork/background)都会继承该属性,startSubagentSpan(session-tracing.ts)也会写入它。当前场景不会产生 subagent span,所以该断言永远不会触发;但一旦场景扩展为调用 subagent,这个断言就会在正确的代码上失败——诱导开发者为了变绿而移除 subagent span 上的 gen_ai.user.id,从而回退了单元测试所保护的行为。—— 失败场景:扩展集成场景以调用 Agent → 负向断言在正确代码上失败 → 开发者为修复测试而移除该属性。
— qwen3.8-max-preview via Qwen Code /review
|
Released in v0.21.1. |



What this PR does
This PR adds an explicit, opt-in end-user identity for Alibaba Cloud ARMS Session Analysis. Operators can configure a stable pseudonymous ID through
telemetry.userIdorQWEN_TELEMETRY_USER_ID; the environment variable takes precedence after trimming, and a blank environment value falls back to settings.The resolved value is attached at span creation as the ARMS extension
gen_ai.user.idand propagated from the interaction through LLM, Tool, and Agent spans, including tool-result continuations and linked-root background agents. The implementation preserves the existing span names, kinds, parenting, retry topology, and outbound propagation behavior.The new identity is not inferred, generated, written to Resource/logs/metrics, placed in outbound Baggage, or dual-written as
enduser.id/user.id. Configuration schema, operator guidance, migration notes, unit coverage, and the permanent GenAI telemetry integration flow are updated together.Why it's needed
ARMS Session Analysis reads its User ID from the span-level
gen_ai.user.idattribute. A Resource attribute such asuser.idremains a generic deployment dimension and leaves the ARMS Session User ID empty. This change lets single-user CLI and one-process-per-user deployments populate that field deliberately without exposing names, email addresses, model credentials, or automatically derived machine identity.Reviewer Test Plan
How to verify
Configure
telemetry.userIdorQWEN_TELEMETRY_USER_ID, export a complete LLM → Tool → LLM turn, and confirm that the interaction, both LLM spans, and the Tool span carry the exact samegen_ai.user.id. Confirm the value is absent from Resource attributes, non-canonical telemetry records,enduser.id,user.id, and outbound Baggage. Repeat without either setting and confirm the attribute is omitted.For precedence and validation, set both sources and confirm the trimmed environment value wins; set a blank environment value and confirm settings wins; provide a non-string settings value and confirm configuration fails rather than exporting
null.Automated verification passed: Core targeted tests 957/957, CLI targeted tests 492/492, GenAI telemetry integration tests 3/3,
npm run build,npm run bundle,npm run typecheck, andnpm run lint.An online ARMS smoke with pseudonymous ID
arms-smoke-20260728-1628showed the exact value on three LLM spans, one Tool span, the Session view, and the User view. Raw LLM and Tool Attributes containedgen_ai.user.id; the Resource panel did not contain the new field or value. Direct content-query syntax still requires the workspace to configure this attribute as an SLS key-value index.Evidence (Before & After)
N/A — no Qwen Code TUI change. Before this change the released CLI ignored
QWEN_TELEMETRY_USER_ID; after this change ARMS Session and User views render and group the configured pseudonymous ID.Tested on
Environment (optional)
macOS, Node.js v24.12.0, local Qwen Code bundle, sandbox disabled for the fake OpenAI integration flow, and Alibaba Cloud ARMS in
cn-hangzhoufor the online smoke.Risk & Scope
telemetry.resourceAttributes.user.idvalues are not promoted automatically; operators using that field for ARMS Session identity should remove it and configure the new span-level setting.Linked Issues
N/A
中文说明
本 PR 做了什么
本 PR 为阿里云 ARMS 会话分析增加显式、按需启用的终端用户身份。运维人员可通过
telemetry.userId或QWEN_TELEMETRY_USER_ID配置稳定的假名化 ID;两个值去除首尾空白后由环境变量优先,空白环境变量会回退到 settings。解析后的值会在 Span 创建时以 ARMS 扩展字段
gen_ai.user.id写入,并从 interaction 传播到 LLM、Tool 和 Agent Span,包括 ToolResult 延续轮次和 linked-root 后台 Agent。实现保持现有 Span 名称、SpanKind、父子关系、重试拓扑和出站传播行为不变。新身份不会被自动推断或生成,不会写入 Resource、日志或指标,不会进入出站 Baggage,也不会双写为
enduser.id/user.id。配置 schema、运维说明、迁移说明、单元测试和永久 GenAI telemetry 集成流程同步更新。为什么需要
ARMS 会话分析从 Span 级
gen_ai.user.id读取 User ID。user.id等 Resource 属性仍然只是通用部署维度,无法填充 ARMS Session User ID。本变更让单用户 CLI 和一进程一用户部署可以主动填充该字段,同时避免暴露姓名、邮箱、模型凭据或自动推断的机器身份。Reviewer 测试计划
如何验证
配置
telemetry.userId或QWEN_TELEMETRY_USER_ID,导出一次完整的 LLM → Tool → LLM 流程,确认 interaction、两个 LLM Span 和 Tool Span 都携带完全相同的gen_ai.user.id。确认该值不存在于 Resource 属性、非 canonical telemetry 记录、enduser.id、user.id和出站 Baggage。删除两个配置后重跑,确认属性完全省略。验证优先级和校验逻辑时,同时设置两个来源并确认去除首尾空白后的环境变量生效;将环境变量设为空白并确认回退到 settings;提供非字符串 settings 值并确认配置失败,而不是导出
null。自动化验证已通过:Core 定向测试 957/957、CLI 定向测试 492/492、GenAI telemetry 集成测试 3/3,以及
npm run build、npm run bundle、npm run typecheck和npm run lint。使用假名化 ID
arms-smoke-20260728-1628的 ARMS 在线 smoke 验证中,三个 LLM Span、一个 Tool Span、Session 视图和 User 视图都显示了完全一致的值。LLM 和 Tool 的原始 Attributes 包含gen_ai.user.id;Resource 面板不包含新字段或该值。直接使用内容查询语法仍要求工作空间将此字段配置为 SLS 键值索引。证据(变更前后)
N/A——Qwen Code TUI 没有变化。变更前,已发布 CLI 会忽略
QWEN_TELEMETRY_USER_ID;变更后,ARMS Session 和 User 视图可以展示并按配置的假名化 ID 聚合。测试平台
环境(可选)
macOS、Node.js v24.12.0、本地 Qwen Code bundle;fake OpenAI 集成流程关闭 sandbox;在线 smoke 使用
cn-hangzhou的阿里云 ARMS。风险与范围
telemetry.resourceAttributes.user.id不会自动提升;依赖它填充 ARMS Session 身份的运维人员应删除旧 Resource 字段,改用新的 Span 级配置。关联 Issue
N/A