feat(telemetry): emit skill/session lifecycle events from the hook#1795
feat(telemetry): emit skill/session lifecycle events from the hook#1795gpanaitescu wants to merge 4 commits into
Conversation
|
Claude finished @gpanaitescu's task in 3m 44s —— View job PR Review: feat(telemetry): emit skill/session lifecycle events from the hook
SummaryExtends the telemetry hook from a single Change-by-Change Review1.
|
| Area | Status | Notes |
|---|---|---|
| Frontmatter | N/A | No skill files changed |
| E2E Tests | N/A | Hook/infra change, no skill behavior changed |
| Skill Body | N/A | No skill files changed |
| References & Assets | OK | TELEMETRY.md updated with two stale lines (see above) |
| Repo Hygiene | OK | No secrets, no hardcoded paths, valid JSON, async hooks, cross-platform bash |
Issues for Manual Review
- CLI co-version dependency: This hook sends
eventNametokens (session-start,completion,session-end) that require UiPath/cli#2815 on the receiving side. If a user has the updated plugin but an older CLI that still hard-codesuip.skills.tool-use, lifecycle events would be emitted under the wrong name or silently dropped. ThetargetCli: "^1.198.0"constraint should enforce this, but worth verifying the CLI-side mapping is deployed before this ships. source/reasonfield bleed: The awk extractor pullssourceandreasonfrom any event's envelope — not onlySessionStart/SessionEnd. If a future Claude Code version adds asourceorreasonfield to e.g.Stoppayloads, those values would silently appear insessionSource/reasononcompletionevents. Low-risk (the doc says "session-start only" / "session-end only"), but worth noting.
Conclusion
Clean, well-structured change. The shell script logic is correct — lifecycle branching, region-scoped extraction, attribution gating, and sanitization all look right. Two stale references in TELEMETRY.md (lines 77 and 89) should be updated before merge to avoid misleading readers. The "How it works" section is a minor doc gap. Recommend fixing the two stale lines and merging.
6906d10 to
4ed5517
Compare
Companion to UiPath/cli#2815 (the ingestion contract). Registers hooks/send-telemetry.sh on SessionStart/SessionEnd/Stop/StopFailure in addition to PostToolUse and maps them to canonical eventName tokens (session-start, session-end, completion; Stop/StopFailure both map to completion, distinguished by outcome, so API-error turns are not lost). Lifecycle events skip the per-call attribution gate — they fire for every session with the plugin installed (the activation-rate denominator). Schema v2: - adds eventName / session_source / reason - renames sessionId -> session_id (canonical casing, matching the CLI command stream after UiPath/cli#2800) - drops environment/baseUrl and the whole resolve_environment path (uip login status + 1h cache): the CLI stamps fresh environment/base_url/region base dimensions on every event since UiPath/cli#2806, so a stale hook-cached copy must not shadow them New hooks/set-session-env.sh — a synchronous SessionStart step that exports UIPATH_SESSION_ID to CLAUDE_ENV_FILE, so native uip command telemetry carries the same session_id and the two streams join. A host-provided value wins, the value is sanitized before being written into the sourced env file, and the step is not gated (writing a variable transmits nothing). Rebased on #1745 (opt-out gate + Codex spawn_agent support): lifecycle events inherit the flipped gate untouched, and the schema-v1 wording in its cross-agent doc section is updated to v2. Tests set UIPATH_TELEMETRY_DISABLED explicitly, so they hold under either polarity. Tests: pytest contract guards (POSIX/CI) run both bash hooks as subprocesses — eventName mapping, lifecycle fields, the v2 key set, drop paths; and the env step's export/host-wins/idempotence/ sanitization. Wired into test-helpers.yml (hooks/** trigger). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4ed5517 to
2bf9a47
Compare
- set-session-env.sh: repair a missing trailing newline on the existing CLAUDE_ENV_FILE before appending, so the export line can never be concatenated onto another hook's partial last line. - send-telemetry.sh: enforce per-event field scoping — session_source only on session-start, reason only on session-end — so a future payload carrying stray source/reason keys on other events cannot bleed into those dimensions. - TELEMETRY.md: "How it works" step 1 now covers the lifecycle events (session-scoped, no per-call gate), not just the PostToolUse flow. - Tests for the newline repair and the no-bleed guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Re the Claude bot review (it ran on a pre-reconciliation head) — point-by-point:
|
|
Claude finished @gpanaitescu's task in 4m 34s —— View job PR Review: feat(telemetry): emit skill/session lifecycle events from the hook
SummaryExtends the telemetry hook from a single The previous Claude review flagged four issues; the author's follow-up commit ( Change-by-Change Review1.
|
| Area | Status | Notes |
|---|---|---|
| Frontmatter | N/A | No skill files changed |
| E2E Tests | N/A | Infrastructure change, not a skill behavior change |
| Skill Body | N/A | No skill files changed |
| References & Assets | OK | TELEMETRY.md thoroughly updated; all prior-review findings resolved |
| Repo Hygiene | OK | No secrets, valid JSON, cross-platform bash, async hooks, scoped changes |
Issues for Manual Review
- CLI co-version pairing: The
targetCli: "^1.198.0"in version-manifest.json doesn't yet encode the v2 dependency on cli#2815. The PR description documents this as a release-train concern (merge both before the 2026-07-12 cut, then verifytargetCligets stamped to^1.199.0). Worth confirming the release-train automation handles this. - Codex lifecycle coverage: The PR description notes Codex gets
session-start/completion"for free" through the same hook, but has nosession-endsignal. Whether that gap matters for Codex session-duration queries is a product decision, not a code issue.
Conclusion
This is a clean, well-implemented PR. The shell logic is correct — lifecycle branching, field scoping enforcement, sanitization, and the synchronous env-file step all handle edge cases carefully. The previous review's findings have all been addressed in b3ace111. Tests provide good contract coverage for both hooks. Documentation is thorough and consistent with the implementation. No blocking issues — recommend merging once the CLI-side pairing (cli#2815) is confirmed on track for the same release cut.
|
Note for reviewers — the red ✗ on "Validate task schema (advisory)" is not from this PR. It fails on two task YAMLs introduced by #1739 ( |
…#2785) Extracts the envelope `model` (Claude Code sends it on SessionStart, Codex on every hook event) and emits it as `agent_model` — the session's main model, distinct from subagentModel (a spawned child's family). Full sanitized slug, no family collapse: the model-comparison views need version granularity. Claude sessions get full coverage at query time by joining on session_id from the session-start event. Rides the same schema v2 bump (still unmerged), avoiding a v3 right after. Region scoping holds: a "model" key embedded in tool output cannot false-match the envelope field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make the Codex event/field parity inspectable instead of implicit (review ask on the event map): - TELEMETRY.md: per-agent availability table — Codex fires SessionStart and Stop under the same names with a matching envelope (session_id / source / model, per the Codex hooks docs), so session-start and completion map unchanged; Codex has no SessionEnd (completion is its terminal signal) and no StopFailure (outcome is always ok); its turn_id / stop_hook_active / last_assistant_message extras are never read; duration_ms is absent (durationMs: null). - TELEMETRY.md: state that cross-stream correlation is Claude Code-only — Codex has no env-file equivalent for hooks, and CODEX_THREAD_ID equals the payload session_id only for the root thread (verified in openai/codex source; subagent threads carry their own thread id), so native-command correlation is not available under Codex today. - map_event_name comment: state the cross-agent contract at the map itself. - pytest: Codex-shaped SessionStart (source + model → session_source + agent_model) and Stop (extras never forwarded, incl. the free-text last_assistant_message) payload guards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
115068e to
4c66818
Compare
Shipping plan — merge BEFORE the Sunday 2026-07-12 release cut
targetClipairing — schedule + post-cut verification, not a merge blocker. The v2 hook must ship paired with the first CLI release carrying UiPath/cli#2815 — a pre-lifecycle CLI treatseventNameas an unknown field and would emit lifecycle payloads as phantomtool-useevents (identifiable/filterable:schemaVersion == 2with emptytoolName). The pairing is enforced by the release train, not by this file:uip skills installpins the skills package to the CLI's own minor, and the skillssprint-release-cutstampstargetClito the paired line at cut time. So: merge this PR and cli#2815 to their mains before the Sunday 2026-07-12 cut (skills cut 06:00 UTC, CLI cut 12:00 UTC; missing it slips the emitter to the Jul 26 train while CLI 1.199 ships ingestion unpaired) — then verify after the cut that the skills1.199release line carriestargetCli: ^1.199.0(the automation's drift check is warn-only). Residual exposure between merge and CLI-1.199 reaching users: only channels tracking the repo directly (marketplace/main dogfooders — overwhelminglyIsInternalUser), bounded by the CLI's self-update.Reconcile with feat(telemetry): support other coding agents and flip gate to opt-out #1745 after it lands— done: rebased on the merged feat(telemetry): support other coding agents and flip gate to opt-out #1745; lifecycle events inherit the flipped opt-out gate (verified: unset sends,1drops,0sends); its "schemaVersionstays1" wording updated to v2.What
Extends the telemetry hook (
hooks/send-telemetry.sh) from a singlePostToolUsetool-use event to skill/session lifecycle events, and adds a synchronous
SessionStart step (
hooks/set-session-env.sh) that exportsUIPATH_SESSION_IDso native
uipcommand telemetry carries the same session id. This is theemitter half of UiPath/cli#2786; it requires the CLI ingestion change in
UiPath/cli#2815 (the CLI resolves the
eventNametoken this hook now sendsto a
uip.skills.<event>event). Co-versioned viaschemaVersion(bumped 1 → 2).Mapping
send-telemetry.shis now registered on four more Claude Code hook events and mapseach to a canonical
eventName:eventNamePostToolUse(existing)tool-useSessionStartsession-startsource→session_sourceStopcompletionoutcome=okStopFailurecompletionoutcome=failure(API-error turn)SessionEndsession-endreasonSchema v2
eventName/session_source/reason.agent_model(UiPath/cli#2785 enrichment, riding the same bump): thesession's main model from the envelope
model— Claude Code sends it onSessionStart, Codex on every hook event. Full sanitized slug (model-comparisonviews need version granularity); distinct from
subagentModel(spawned child'sfamily). Claude sessions get full coverage at query time via the
session_idjoin from
session-start. Needs a small CLI follow-up to accept the field inALLOWED_FIELDS(until then it is silently dropped — additive-safe).sessionId→session_id— the canonical casing, matching the CLIcommand stream after UiPath/cli#2800 (
uip trackstill accepts and maps thelegacy spelling, so no flag-day).
environment/baseUrland the wholeresolve_environmentpath(the
uip login statuscall + 1h cache + cache file): since UiPath/cli#2806the CLI stamps fresh
environment/base_url/regionbase dimensions on everyevent from its own auth context, so a stale hook-cached copy must not shadow
them. Removes ~45 lines and the hook's only subprocess call.
Design
gate and tool-field derivation, and fire for every session where this plugin is
installed (the activation-rate denominator).
StopFailure→completion(outcome=failure)so API-error turns aren't lostfrom completion/abandonment metrics.
Stop/StopFailureare mutually exclusiveper turn (Claude Code docs), so
completioncannot double-count.set-session-env.sh(new, synchronous): reads the SessionStart payload'ssession_idand appendsexport UIPATH_SESSION_ID='<id>'toCLAUDE_ENV_FILE,so every subsequent
uipcommand inherits it and the CLI stamps the samesession_idon native command telemetry (UiPath/cli#2800) — skills events andcommand events join at query time. Host-provided value wins; idempotent;
value sanitized to
[A-Za-z0-9._-]before being written into the sourced envfile; not gated on
UIPATH_TELEMETRY_DISABLED(writing a variable transmitsnothing — the CLI's gate governs whether any event carrying it is sent).
opt-out gate as tool-use — this PR neither moves nor duplicates the gate line,
and its tests set
UIPATH_TELEMETRY_DISABLEDexplicitly so they hold undereither polarity.
Scope
This is primarily the Claude Code emitter. Under Codex — whose hooks.json
support and payload envelope match Claude's (#1745) —
SessionStart/Stopflowthrough this same hook already, so Codex gets
session-start/completionforfree (it has no session-end hook;
completionis its terminal signal).Gemini CLI and the Cursor CLI expose the same lifecycle moments under
different hook names/registration formats and are separate follow-ups.
Cursor cloud agents are out of scope (ephemeral remote VM — no local
uip, noauth identity, no session-start/-end trigger point).
Testing
test_send_telemetry_hook.py— eventNamemapping, lifecycle fields, the v2 key set (canonical
session_id/session_source,no
environment/baseUrl/legacy camel keys), drop paths;test_set_session_env_hook.py— export line, host-wins, idempotence, hostilesession-id sanitization, skip paths. Both wired into
test-helpers.yml.uip): all five Claude hook payloads produce thecorrect v2 JSON; a
UIPATH_SKILL=uipath-platform uip or assets listtool callstill attributes cleanly (
uipSubcommand: "or assets").paths: unset sends,
1drops,0sends; v2 envelope unchanged.uip track(CLI'stelemetry-capture.ts):maps to
uip.skills.session-start/uip.skills.completion,eventNameconsumedand not emitted, session id → stable
uuid#hash.bash -nclean on both hooks;hooks.json/version-manifest.jsonvalid JSON.🤖 Generated with Claude Code