Skip to content

feat(daemon): mark Claude Code cloud session telemetry - #330

Merged
AnnatarHe merged 2 commits into
mainfrom
claude/magical-einstein-v9kkh0
Oct 22, 2026
Merged

AnnatarHe merged 2 commits into
mainfrom
claude/magical-einstein-v9kkh0

Conversation

@AnnatarHe

@AnnatarHe AnnatarHe commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What

Claude Code cloud sessions (claude.ai/code) are tracked through the setup in the cloud tracking guide, but nothing marks them as cloud sessions. This adds the CLI part of marking them.

  • What Claude Code sends: it tags every event and metric of a cloud session with the standard attribute ccr.session.id, which is the VM's CLAUDE_CODE_REMOTE_SESSION_ID (cse_…). The daemon already forwards it in attributes.
  • Fallback in the daemon: in a cloud VM the daemon is started by the session's SessionStart hook, so its environment has CLAUDE_CODE_REMOTE=true and CLAUDE_CODE_REMOTE_SESSION_ID. NewAICodeOtelProcessor now reads them once with model.CloudSessionIDFromEnv. It adds ccr.session.id to Claude Code events and metrics that don't carry one.
    • A value Claude Code sent wins.
    • Codex records are left alone.
    • The value is seeded before record attributes, the same way resource attributes are, so it can't be crowded out by the 64-key cap.
    • Stable ot1: ids hash the OTLP proto, so they don't change.
  • Local machines are unaffected: the docs say CLAUDE_CODE_REMOTE is never true locally.

The server side (storing the id on the session summary, GraphQL isCloud / cloudSessionUrl, the PR comment heading) is in a separate server PR.

Fact check

There is no CLAUDE_CODE_CLOUD. Cloud environments documents CLAUDE_CODE_REMOTE and CLAUDE_CODE_REMOTE_SESSION_ID. Monitoring lists ccr.session.id among the standard attributes. I checked both inside a running cloud session (Claude Code 2.1.308) and in the environment of the daemon its hook started.

Tests

  • TestCloudSessionIDFromEnv (model) and TestCloudSessionIDFallback (daemon): the id is filled in when missing, Claude Code's value wins, metrics get it too, Codex is untouched, and nothing is added locally.
  • daemon/main_test.go clears both variables. Without it, existing tests that compare whole attribute maps fail when the suite runs inside a cloud session.
  • go vet ./... is clean. go test ./daemon/ ./model/ ./stloader/ passes.
  • ./commands/ (TestTrackTestSuite) fails in this cloud VM with or without this change, because a real daemon owns /tmp/shelltime.sock there.

Generated by Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Claude Code tags every event and metric of a cloud session (claude.ai/code)
with ccr.session.id, the VM's CLAUDE_CODE_REMOTE_SESSION_ID. In a cloud VM
the daemon is started by the session's SessionStart hook, so it inherits
CLAUDE_CODE_REMOTE=true and the session id: it now adds ccr.session.id to
Claude Code records that don't carry one, so the server can mark the
session as a cloud session. A value Claude Code sent wins, Codex records
are left alone, and nothing changes on a local machine.

daemon/main_test.go clears both variables so the suite behaves the same
inside a cloud session as on CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qinc9uT7wXFFuo7yaKBHKq
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-22T10:28:34.295999Z
We couldn’t complete this review. Comment @codex review to try again.
a4ee21d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

⏱️ shelltime track performance

✅ No significant change in shelltime track latency

Comparing main @ 4d80820 → #330 @ 854461e. Each scenario execs the binary the way the shell hooks do; values are the median of 10 interleaved rounds. Lower is better.

Scenario base head Δ p
Startup/floor 223 µs ±37% 222 µs ±17% ~ 0.912 A/A
Startup/version 2.42 ms ±26% 2.49 ms ±12% ~ 0.739 ✅
Track/daemon/pre 2.49 ms ±27% 2.71 ms ±46% ~ 0.190 ✅
Track/daemon/post 2.53 ms ±27% 2.59 ms ±19% ~ 0.853 ✅
Track/direct/pre 2.72 ms ±19% 2.56 ms ±70% ~ 1.000 ✅
Track/direct/post 4.60 ms ±34% 5.02 ms ±16% ~ 0.529 ✅
Track/direct/post-sync 6.13 ms ±21% 6.25 ms ±20% ~ 0.796 ✅

Binary size: 19.2 MiB → 19.2 MiB (+0.0%).

CPU, tail latency and memory

Per exec, base → head (Δ when significant).

Scenario p95 wall user CPU sys CPU peak RSS
Startup/floor 262 µs → 269 µs 191 µs → 197 µs 8 µs → 8 µs 9.0 MiB → 9.0 MiB
Startup/version 3.63 ms → 3.96 ms 1.67 ms → 1.65 ms 847 µs → 928 µs 15.6 MiB → 15.6 MiB
Track/daemon/pre 3.13 ms → 3.53 ms 1.59 ms → 1.77 ms (+11.03%) 843 µs → 967 µs 16.5 MiB → 16.5 MiB
Track/daemon/post 3.66 ms → 2.89 ms 1.76 ms → 1.73 ms 873 µs → 924 µs 16.4 MiB → 16.4 MiB
Track/direct/pre 4.09 ms → 3.64 ms 1.82 ms → 1.67 ms 985 µs → 905 µs 16.4 MiB → 16.4 MiB
Track/direct/post 5.41 ms → 7.33 ms 3.28 ms → 2.98 ms 1.87 ms → 1.93 ms 17.6 MiB → 17.6 MiB
Track/direct/post-sync 8.46 ms → 8.89 ms 4.35 ms → 4.18 ms 2.09 ms → 2.33 ms 19.6 MiB → 19.6 MiB
benchstat
goos: linux
goarch: amd64
pkg: github.com/malamtime/cli/perf
cpu: Intel(R) Xeon(R) Processor
                         │     base     │                head                 │
                         │    sec/op    │    sec/op     vs base               │
Startup/floor-2            223.3µ ± 37%   221.9µ ± 17%       ~ (p=0.912 n=10)
Startup/version-2          2.418m ± 26%   2.490m ± 12%       ~ (p=0.739 n=10)
Track/daemon/pre-2         2.492m ± 27%   2.711m ± 46%       ~ (p=0.190 n=10)
Track/daemon/post-2        2.529m ± 27%   2.589m ± 19%       ~ (p=0.853 n=10)
Track/direct/pre-2         2.724m ± 19%   2.562m ± 70%       ~ (p=1.000 n=10)
Track/direct/post-2        4.603m ± 34%   5.016m ± 16%       ~ (p=0.529 n=10)
Track/direct/post-sync-2   6.129m ± 21%   6.250m ± 20%       ~ (p=0.796 n=10)
geomean                    2.215m         2.271m        +2.53%

                         │     base     │                head                 │
                         │  p50-sec/op  │  p50-sec/op   vs base               │
Startup/floor-2            213.2µ ± 37%   216.9µ ±  9%       ~ (p=0.739 n=10)
Startup/version-2          2.180m ± 23%   2.319m ± 18%       ~ (p=0.739 n=10)
Track/daemon/pre-2         2.349m ± 17%   2.590m ± 70%       ~ (p=0.190 n=10)
Track/daemon/post-2        2.311m ± 31%   2.390m ± 19%       ~ (p=0.912 n=10)
Track/direct/pre-2         2.395m ± 19%   2.422m ± 92%       ~ (p=0.631 n=10)
Track/direct/post-2        4.510m ± 21%   4.529m ± 21%       ~ (p=0.739 n=10)
Track/direct/post-sync-2   5.897m ± 25%   5.825m ± 15%       ~ (p=0.529 n=10)
geomean                    2.066m         2.130m        +3.10%

                         │     base     │                head                 │
                         │  p95-sec/op  │  p95-sec/op   vs base               │
Startup/floor-2            262.3µ ± 65%   268.5µ ± 28%       ~ (p=0.796 n=10)
Startup/version-2          3.630m ± 39%   3.960m ± 25%       ~ (p=0.739 n=10)
Track/daemon/pre-2         3.126m ± 59%   3.529m ± 49%       ~ (p=0.579 n=10)
Track/daemon/post-2        3.663m ± 40%   2.887m ± 60%       ~ (p=0.684 n=10)
Track/direct/pre-2         4.091m ± 41%   3.637m ± 52%       ~ (p=1.000 n=10)
Track/direct/post-2        5.408m ± 64%   7.330m ± 21%       ~ (p=0.247 n=10)
Track/direct/post-sync-2   8.457m ± 22%   8.891m ± 32%       ~ (p=0.853 n=10)
geomean                    2.970m         3.070m        +3.34%

                         │     base     │                head                 │
                         │  peak-rss-B  │  peak-rss-B   vs base               │
Startup/floor-2            8.994Mi ± 1%   9.002Mi ± 1%       ~ (p=0.446 n=10)
Startup/version-2          15.61Mi ± 0%   15.61Mi ± 0%       ~ (p=0.378 n=10)
Track/daemon/pre-2         16.52Mi ± 0%   16.52Mi ± 0%       ~ (p=0.897 n=10)
Track/daemon/post-2        16.40Mi ± 0%   16.41Mi ± 0%       ~ (p=0.239 n=10)
Track/direct/pre-2         16.39Mi ± 0%   16.39Mi ± 0%       ~ (p=0.469 n=10)
Track/direct/post-2        17.63Mi ± 0%   17.63Mi ± 0%       ~ (p=0.698 n=10)
Track/direct/post-sync-2   19.58Mi ± 0%   19.60Mi ± 0%       ~ (p=0.516 n=10)
geomean                    15.50Mi        15.51Mi       +0.03%

                         │     base     │                  head                  │
                         │  sys-sec/op  │  sys-sec/op    vs base                 │
Startup/floor-2            7.695µ ± 76%   8.140µ ±  48%       ~ (p=0.842 n=10+9)
Startup/version-2          847.2µ ± 70%   927.5µ ±  38%       ~ (p=0.739 n=10)
Track/daemon/pre-2         843.3µ ± 94%   966.9µ ±  99%       ~ (p=0.529 n=10)
Track/daemon/post-2        873.0µ ± 72%   923.6µ ±  48%       ~ (p=0.739 n=10)
Track/direct/pre-2         985.3µ ± 43%   905.1µ ± 124%       ~ (p=0.579 n=10)
Track/direct/post-2        1.874m ± 29%   1.930m ±  31%       ~ (p=0.393 n=10)
Track/direct/post-sync-2   2.090m ± 38%   2.326m ±  27%       ~ (p=0.631 n=10)
geomean                    565.6µ         598.1µ         +5.75%

                         │     base     │                 head                 │
                         │ user-sec/op  │ user-sec/op   vs base                │
Startup/floor-2            191.3µ ± 37%   196.6µ ± 19%        ~ (p=0.579 n=10)
Startup/version-2          1.671m ± 11%   1.650m ±  9%        ~ (p=1.000 n=10)
Track/daemon/pre-2         1.593m ±  8%   1.769m ± 22%  +11.03% (p=0.035 n=10)
Track/daemon/post-2        1.764m ± 13%   1.727m ±  8%        ~ (p=0.631 n=10)
Track/direct/pre-2         1.822m ± 11%   1.665m ± 37%        ~ (p=0.353 n=10)
Track/direct/post-2        3.277m ± 22%   2.979m ± 35%        ~ (p=1.000 n=10)
Track/direct/post-sync-2   4.354m ± 16%   4.175m ± 15%        ~ (p=0.165 n=10)
geomean                    1.568m         1.540m         -1.82%

                │    head     │
                │  sys-ns/op  │
Startup/floor-2   0.000 ± ∞ ¹
¹ need >= 6 samples for confidence interval at level 0.95

Δ compares medians; ~ means no significant difference (Mann-Whitney U, p ≥ 0.05). A scenario is flagged when it is more than 10% and more than 250µs slower with p < 0.05. Startup/floor runs true, not shelltime: it is an A/A check of runner noise. Runner: linux/amd64, Intel(R) Xeon(R) Processor. go1.27.1, 10 rounds × 100 execs per scenario and binary.

@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
daemon/aicode_otel_processor.go 87.50% 1 Missing ⚠️
Flag Coverage Δ
unittests 86.19% <90.90%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
model/aicode_cloud_session.go 100.00% <100.00%> (ø)
daemon/aicode_otel_processor.go 89.45% <87.50%> (-0.03%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shelltime

shelltime Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

☁️ Claude Code cloud · Add explicit Claude Code cloud-session tracking

Added explicit Claude Code cloud-session metadata across the CLI, server, and web UI, including remote session IDs and test coverage, then ran repository reviews and addressed valid findings.

💰 Cost 🔢 Tokens ⏱️ Duration 🧠 Models 💬 Prompts ✏️ Lines
$30.06 94.1M 1h 32m (36m active) claude-opus-5-5, claude-haiku-5-5 3 +339 / −108

94.1M tokens: 43K input · 207.5K output · 92.7M cache read · 1.1M cache write

📝 AI summary

Implemented first-class cloud-session tracking based on the documented Claude Code cloud environment variables. The CLI model in cli/model/aicode_cloud_session.go now detects cloud execution and captures metadata such as CLAUDE_CODE_REMOTE_SESSION_ID; corresponding tests were added or updated in aicode_cloud_session_test.go.

Extended the server-side session and summary models in server/model/cloud_session.go and server/ent/schema/ccsessionsummary.go, with coverage in cloud_session_test.go, so cloud status and remote-session information are persisted and exposed rather than treated as ordinary tracking messages. Updated web/src/components/ai-session/cloud-session-badge.tsx to surface the cloud-session distinction in the UI. Finally, triggered /claude review across the repositories and applied fixes for findings judged to be real issues.

View this session on ShellTime →

Updated 2026-10-23 10:29 UTC by ShellTime · totals for the whole session so far · the session page is visible to its owner

Copy link
Copy Markdown
Contributor Author

@claude review


Generated by Claude Code

@claude

claude Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @AnnatarHe's task in 23s —— View job


Review of #330: mark Claude Code cloud session telemetry

I read the full diff (git diff origin/main...HEAD) and checked setAttribute and parseMetric in daemon/aicode_otel_processor.go. I did not run the tests. CI shows no perf change and codecov shows full coverage of the modified lines.

Verdict: looks good. The change is small and well tested. I have one inaccuracy to fix and a few optional notes.

Findings

  1. The metric path doesn't match the "seeded before record attributes" claim. (daemon/aicode_otel_processor.go:498)

    • The PR description and the log-path comment say the id is seeded before record attributes, so the 64-key cap can't crowd it out.
    • That holds for logs. parseLogRecord calls fillCloudSessionID after the resource attributes and before lr.GetAttributes().
    • For metrics, fillCloudSessionID runs after applyResourceAttributesToMetric and applyMetricAttribute.
    • setAttribute returns without adding the key when len(attrs) >= AICodeOtelMaxAttributes and the key is new. If a metric data point already fills the cap, the id is silently dropped.
    • This is unlikely, since metric data points normally carry few attributes. Still, the behavior differs between the two paths.
    • Options: move the fill before the applyMetricAttribute loop. A ccr.session.id on the data point then overwrites it, which is the same "Claude Code wins" rule as for logs. Or reword the description and comment so they only claim this for logs.
  2. The empty-id case is not tested. (model/aicode_cloud_session_test.go)

    • {CLAUDE_CODE_REMOTE=true, ID=" "} should return "" after TrimSpace. A one-line table case would cover it.
  3. daemon/main_test.go is package-wide.

    • os.Unsetenv in TestMain is fine.
    • t.Setenv in TestCloudSessionIDFallback restores to the cleared state afterward, so there is no leakage.
    • Any future TestMain in the daemon package would need to merge with this one.
  4. Behavior note, not a bug.

    • The fallback tags every Claude Code record the daemon receives with the VM's session id. That is correct for a cloud VM, where the daemon is the session's own.
    • It would be wrong if a daemon started in a cloud session were ever shared with other Claude Code processes. That doesn't apply to the VM-local setup in the description.

The CLAUDE.md update matches the code. The Codex-untouched and "Claude Code's value wins" tests cover the main behaviors.

--- · branch claude/magical-einstein-v9kkh0

…butes

Metrics added ccr.session.id after the data point's own attributes, so a
data point already at the 64-attribute cap dropped it. It is now seeded
before them, like on events; a ccr.session.id on the data point still
wins. Also covers a blank CLAUDE_CODE_REMOTE_SESSION_ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qinc9uT7wXFFuo7yaKBHKq

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Responses by finding number:

  1. Fixed in 854461e. Metrics now seed ccr.session.id before the data point's attributes, the same as events. A value on the data point still wins. I added a test with a data point at the 64-attribute cap: it fails on the previous commit (the id comes back nil) and passes now.
  2. Added a blank id case to TestCloudSessionIDFromEnv.
  3. Leaving as is. There's no other TestMain in daemon, and a future one would just merge into this one.
  4. Agreed, no change. The daemon belongs to the cloud VM: the SessionStart hook starts it inside the session. On a local machine CLAUDE_CODE_REMOTE is never true, so nothing gets tagged.

Generated by Claude Code

@AnnatarHe
AnnatarHe merged commit 5d8553c into main Oct 22, 2026
5 checks passed
@AnnatarHe
AnnatarHe deleted the claude/magical-einstein-v9kkh0 branch October 22, 2026 10:56
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.

2 participants