Skip to content

feat: record telemetry exception events - #839

Merged
chrisdoc merged 11 commits into
mainfrom
feat/otel-exception-tracking
Aug 1, 2026
Merged

feat: record telemetry exception events#839
chrisdoc merged 11 commits into
mainfrom
feat/otel-exception-tracking

Conversation

@chrisdoc

@chrisdoc chrisdoc commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • record tool and process exceptions as OpenTelemetry span exception events
  • preserve Sentry failure capture and safe diagnostic attributes
  • document telemetry behavior and add lifecycle test seams

Verification

  • npm run build
  • npx vitest run packages/node/src/utils/telemetry.test.ts packages/node/src/index.test.ts

Known workspace-wide validation remains blocked by missing @modelcontextprotocol/* packages in the current environment.

Summary by CodeRabbit

  • New Features

    • Added privacy-safe telemetry for tool failures, uncaught exceptions, and unhandled promise rejections.
    • Telemetry now captures relevant error details without affecting tool responses.
    • Added automatic Git hook installation during package preparation, with clear warnings if unavailable.
  • Documentation

    • Clarified telemetry behavior and error-monitoring coverage.
  • Bug Fixes

    • Improved CLI package smoke tests for filesystem compatibility.

✨ PR Description

Purpose: Implement comprehensive exception tracking for process-level and tool-level errors through OpenTelemetry spans and Sentry integration.

Main changes:

  • Added exception field to ToolCompletionObservation and implemented recordTelemetryException() function to capture exception events with contextual attributes
  • Installed process-level exception handlers for uncaught exceptions and unhandled promise rejections with cleanup on server shutdown
  • Enhanced tool observer to record both thrown and returned errors as telemetry exceptions with sanitized diagnostic metadata

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Description using Guidelines Learn how

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds OpenTelemetry tracking for tool and process exceptions, integrates cleanup into stdio and HTTP server lifecycles, installs Git hooks during package preparation, and updates the CLI packaging smoke test for cross-device directory handling.

Changes

Exception telemetry

Layer / File(s) Summary
Exception recording contract and implementation
packages/node/src/utils/telemetry.ts, packages/node/src/utils/telemetry.test.ts
Exceptions are normalized and recorded on active or supplied spans with attributes and error status.
Tool failure telemetry integration
packages/node/src/utils/tool-observer.ts, packages/node/src/utils/tool-observer.test.ts
Thrown and returned tool errors record diagnostic metadata without changing tool responses.
Process exception tracking and server lifecycle
packages/node/src/index.ts, packages/node/src/index.test.ts, README.md, .changeset/quiet-telemetry-opt-out.md
Uncaught exceptions and unhandled rejections use removable listeners. Stdio and HTTP paths clean up tracking before telemetry flushes or error rethrows.

Developer tooling and CLI packaging

Layer / File(s) Summary
Git hook installation wiring
scripts/install-git-hooks.mjs, package.json, Dockerfile, .dockerignore, AGENTS.md
Package preparation invokes hook installation through mise. Docker builds include the installer, and workflow guidance forbids hook bypassing.
Filesystem-safe CLI packaging smoke test
packages/cli/tests/npm-pack-smoke.mjs, .changeset/tidy-cli-smoke.md
Directory backup and restoration use recursive copy-and-remove operations instead of rename operations.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NodeServer
  participant ProcessExceptionTracking
  participant Process
  participant Telemetry
  NodeServer->>ProcessExceptionTracking: installProcessExceptionTracking()
  Process->>ProcessExceptionTracking: emit exception event
  ProcessExceptionTracking->>Telemetry: record exception with source
  NodeServer->>ProcessExceptionTracking: clean up listeners
  NodeServer->>Telemetry: flush telemetry
Loading

Possibly related PRs

Suggested labels: 30 min review

Poem

A rabbit logs errors, quiet and bright,
With spans that remember each process-flight.
Hooks hop in place when packages prepare,
Smoke tests cross devices without a scare.
Telemetry thumps: “All caught!” in the night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: recording telemetry exception events.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/otel-exception-tracking

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Worker preview

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

MCP tool token cost

Measured with o200k_base over the complete json-serialized mcp tools/list result payload: { tools }.
Targets are advisory except the enforced total-token budget.

Metric Current Target Status
Tools 26 ≤ 20 Above target
Total tokens 7964 ≤ 8900 Within target
Average tokens/tool 306.31 < 600 Within target

Component totals

Component Tokens
name 153
description 560
inputSchema 3400
outputSchema 3051
annotations 606

Change from baseline

Metric Baseline Current Delta
Tools 26 26 0
Total tokens 7964 7964 0
Average tokens/tool 306.31 306.31 0

Per-tool changes

Tool Baseline Current Delta
create-body-measurement 328 328 0
create-exercise-template 354 354 0
create-routine 420 420 0
create-routine-folder 109 109 0
create-workout 532 532 0
get-body-measurement 300 300 0
get-body-measurements 372 372 0
get-exercise-history 247 247 0
get-exercise-template 172 172 0
get-exercise-templates 254 254 0
get-routine 332 332 0
get-routine-folder 142 142 0
get-routine-folders 224 224 0
get-routines 277 277 0
get-training-summary 624 624 0
get-user-info 133 133 0
get-workout 322 322 0
get-workout-count 109 109 0
get-workout-events 499 499 0
get-workouts 273 273 0
replace-workout-exercises 416 416 0
search-exercise-templates 258 258 0
search-routines 280 280 0
update-body-measurement 328 328 0
update-routine 423 423 0
update-workout 232 232 0

Component changes

Component Delta
name 0
description 0
inputSchema 0
outputSchema 0
annotations 0

Per-tool breakdown

Tool name description inputSchema outputSchema annotations Total Share of total
get-training-summary 5 30 31 531 19 624 7.84%
create-workout 5 20 470 0 31 532 6.68%
get-workout-events 6 22 84 360 19 499 6.27%
update-routine 5 18 363 0 31 423 5.31%
create-routine 5 20 358 0 31 420 5.27%
replace-workout-exercises 7 17 354 0 32 416 5.22%
get-body-measurements 7 24 62 252 19 372 4.67%
create-exercise-template 6 18 292 0 32 354 4.45%
get-routine 5 23 31 247 18 332 4.17%
create-body-measurement 7 24 259 0 32 328 4.12%
update-body-measurement 7 24 259 0 32 328 4.12%
get-workout 5 23 33 235 18 322 4.04%
get-body-measurement 7 25 43 198 19 300 3.77%
search-routines 5 24 42 182 19 280 3.52%
get-routines 5 23 62 160 19 277 3.48%
get-workouts 5 25 47 169 19 273 3.43%
search-exercise-templates 7 25 113 86 19 258 3.24%
get-exercise-templates 7 21 62 137 19 254 3.19%
get-exercise-history 6 22 58 134 19 247 3.1%
update-workout 5 17 173 0 31 232 2.91%
get-routine-folders 7 15 62 112 20 224 2.81%
get-exercise-template 6 23 33 83 19 172 2.16%
get-routine-folder 6 22 31 56 19 142 1.78%
get-user-info 5 20 14 67 19 133 1.67%
create-routine-folder 6 15 50 0 32 109 1.37%
get-workout-count 6 20 14 42 19 109 1.37%

Per-component counts are diagnostic and non-additive because keys and separators live in complete tool objects. Per-tool counts encode each complete tool object independently. The total encodes the complete { tools } envelope, so punctuation and separators mean the per-tool values need not sum exactly to the total.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Record tool/process exceptions as OpenTelemetry span exception events

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Record tool failures as OpenTelemetry span exception events with privacy-safe attributes.
• Track uncaughtException/unhandledRejection during Node runtime and clean up on shutdown.
• Update docs, changeset, and tests to reflect telemetry/opt-out behavior and new seams.
Diagram

graph TD
A["core/tool-runtime"] --> B["ToolCompletionObservation"] --> C["node/tool-observer"]
C --> D["recordTelemetryException"] --> E["Active OTel span"]
H["node/index lifecycle"] --> G["installProcessExceptionTracking"]
F{{"Node process"}} --> G --> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Adopt official OTel process instrumentation
  • ➕ Leverages maintained packages (e.g., process/runtime instrumentation) for uncaughtException/unhandledRejection handling
  • ➕ Potentially more consistent semantic conventions and future compatibility
  • ➖ Adds dependencies and config surface area
  • ➖ Harder to unit test deterministically without heavy mocking
  • ➖ Less control over privacy-safe attribute selection
2. Emit dedicated spans for process exceptions
  • ➕ Captures process-level failures even when no active span exists
  • ➕ Clear separation between tool spans and runtime failures
  • ➖ Requires deciding parent/trace context (often none) and increases trace noise
  • ➖ More exporter volume and sampling complexity compared to span events

Recommendation: Current approach is a good fit: recordException on the active span for tool failures preserves trace context and stays privacy-bounded, while best-effort process listeners provide additional coverage without risking MCP behavior. Consider official process instrumentation only if this grows beyond minimal uncaught/unhandled tracking or requires richer runtime telemetry.

Files changed (10) +120 / -3

Enhancement (5) +109 / -2
observation.tsExtend ToolCompletionObservation with raw exception field +1/-0

Extend ToolCompletionObservation with raw exception field

• Adds an optional 'exception?: unknown' to the tool completion observation contract so downstream observers can access the thrown value.

packages/core/src/observation.ts

tool-runtime.tsCapture thrown exceptions into completion observations +1/-0

Capture thrown exceptions into completion observations

• When a tool handler throws, the runtime now includes the original exception object in the completion payload before rethrowing.

packages/core/src/tools/tool-runtime.ts

index.tsInstall and clean up process-level exception tracking +4/-0

Install and clean up process-level exception tracking

• Installs 'uncaughtException'/'unhandledRejection' tracking at startup and ensures listeners are removed during stdio/HTTP shutdown paths.

packages/node/src/index.ts

telemetry.tsAdd exception event recording and process exception listener installer +56/-1

Add exception event recording and process exception listener installer

• Adds 'recordTelemetryException()' to record span exception events with optional safe attributes and mark span status ERROR. Adds 'installProcessExceptionTracking()' to attach/remove process exception listeners, guarded by 'HEVY_MCP_TELEMETRY' and wrapped to never affect MCP behavior.

packages/node/src/utils/telemetry.ts

tool-observer.tsRecord OTel span exception events for tool failures +47/-1

Record OTel span exception events for tool failures

• Imports and calls 'recordTelemetryException' when tools either throw errors or return error responses, attaching bounded diagnostic attributes (category/code/status/method/endpoint). Preserves existing Sentry capture behavior and maintains best-effort isolation from MCP responses.

packages/node/src/utils/tool-observer.ts

Tests (3) +5 / -0
index.test.tsMock process exception tracking seam in Node entrypoint tests +2/-0

Mock process exception tracking seam in Node entrypoint tests

• Extends telemetry module mocks to include 'installProcessExceptionTracking' and its cleanup function so lifecycle behavior can be exercised without real process listeners.

packages/node/src/index.test.ts

telemetry.test.tsUpdate OTel API mocks for exception event recording +2/-0

Update OTel API mocks for exception event recording

• Extends OpenTelemetry API mocks to include 'SpanStatusCode.ERROR' and 'trace.getActiveSpan' needed by exception recording helpers.

packages/node/src/utils/telemetry.test.ts

tool-observer.test.tsMock recordTelemetryException in tool observer tests +1/-0

Mock recordTelemetryException in tool observer tests

• Updates the telemetry mock surface to include 'recordTelemetryException' for verifying observer behavior without requiring real OTel spans.

packages/node/src/utils/tool-observer.test.ts

Documentation (1) +1 / -1
README.mdDocument exception event behavior in telemetry setting table +1/-1

Document exception event behavior in telemetry setting table

• Updates the 'HEVY_MCP_TELEMETRY' documentation to mention privacy-safe tool/process exception events in OTLP traces and clarify expected monitoring behavior.

README.md

Other (1) +5 / -0
quiet-telemetry-opt-out.mdAdd changeset entry for HEVY_MCP_TELEMETRY opt-out +5/-0

Add changeset entry for HEVY_MCP_TELEMETRY opt-out

• Introduces a patch changeset documenting the master 'HEVY_MCP_TELEMETRY=0' opt-out for local telemetry behavior.

.changeset/quiet-telemetry-opt-out.md

@gitstream-cm gitstream-cm Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ PR Review

The PR adds OpenTelemetry span exception events for tool and process errors. The tool-level exception recording looks correct, but there are two functional bugs: the process-level exception handlers will silently no-op because trace.getActiveSpan() returns nothing outside a span context, and the module-level listener installation leaks listeners on early-exit CLI paths. The changeset description also does not reflect the actual changes introduced.

3 issues detected:

🐞 Bug - `trace.getActiveSpan()` returns `undefined` when called from `uncaughtException`/`unhandledRejection` callbacks because they execute outside any async context that propagates an active span.

Details: recordTelemetryException relies on trace.getActiveSpan() to obtain a span to record the exception on. Process-level events (uncaughtException, unhandledRejection) fire asynchronously outside any active OpenTelemetry context, so getActiveSpan() will always return undefined and the function will silently return without recording anything. The intended feature of capturing process-level exceptions in telemetry will never fire.

File: packages/node/src/utils/telemetry.ts (60-62)

🐞 Bug - The cleanup returned by `installProcessExceptionTracking` is only reachable through `installGracefulShutdown`'s `onComplete`, which is never registered on early-exit CLI paths, causing a permanent listener leak.

Details: installProcessExceptionTracking() is invoked at module evaluation time (line 33), installing uncaughtException and unhandledRejection listeners on the process immediately. The returned cleanup function is only called inside the onComplete callbacks registered by installGracefulShutdown. When the process exits early due to CLI flags (--version, --help) or a startup error before installGracefulShutdown is called, the listeners are never removed. Node.js will emit a MaxListenersExceeded warning in tests and the listeners remain active for the process lifetime.

File: packages/node/src/index.ts (33-33)

🧹 Maintainability - The changeset message describes a different, unrelated feature and will produce a misleading changelog entry for this release. 🛠️

Details: The changeset description reads "Add a master HEVY_MCP_TELEMETRY=0 opt-out for local telemetry", which describes a pre-existing feature unrelated to this PR. The actual user-facing change is that enabled OTLP telemetry now captures tool exception events and process-level exception events. Per project rules, a versioned changeset is required for user-facing, runtime-visible changes, and its description should accurately reflect what changed.

File: .changeset/quiet-telemetry-opt-out.md (5-5)

🛠️ A suggested code correction is included in the review comments.

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Review using Guidelines Learn how

Comment thread .changeset/quiet-telemetry-opt-out.md Outdated
"hevy-mcp": patch
---

Add a master `HEVY_MCP_TELEMETRY=0` opt-out for local telemetry.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Maintainability - Misleading Changeset Description: Update the changeset description to describe the new exception-tracking behaviour, for example: "Record tool thrown-error and returned-error exceptions as OpenTelemetry span exception events; add process-level uncaughtException and unhandledRejection telemetry tracking."

Suggested change
Add a master `HEVY_MCP_TELEMETRY=0` opt-out for local telemetry.
Record tool thrown-error and returned-error exceptions as OpenTelemetry span exception events; add process-level uncaughtException and unhandledRejection telemetry tracking.
Is this review accurate? Use 👍 or 👎 to rate it

If you want to tell us more, use /gs feedback e.g. /gs feedback this review doesn't make sense, I disagree, and it keeps repeating over and over

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Unit Test Results

  1 files   64 suites   7s ⏱️
657 tests 657 ✅ 0 💤 0 ❌
663 runs  663 ✅ 0 💤 0 ❌

Results for commit e4087e5.

♻️ This comment has been updated with latest results.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
packages/node/src/index.test.ts (1)

32-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Retain the cleanup spy for lifecycle assertions.

Line 32 returns an anonymous cleanup mock and discards its reference. Tests cannot verify cleanup during stdio or HTTP completion. Store the returned cleanup spy in testDoubles, then assert it runs on successful shutdown and failure paths.

As per coding guidelines, unit tests must pass using npx vitest run --exclude tests/integration/**.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/node/src/index.test.ts` at line 32, Update the test double for
installProcessExceptionTracking in testDoubles to retain the returned cleanup
spy instead of discarding it. Add lifecycle assertions verifying the cleanup spy
runs during both successful stdio/HTTP completion and failure paths, while
keeping the tests passing with the standard Vitest command excluding integration
tests.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/node/src/index.ts`:
- Line 33: Create process exception tracking inside each exported server
lifecycle, such as runStdioServer and runServer, rather than at module import
via cleanupProcessExceptionTracking. Ensure the per-invocation cleanup runs both
when startup fails before installGracefulShutdown and during graceful shutdown,
so subsequent invocations install fresh process listeners.
- Line 33: Update installProcessExceptionTracking and its cleanup flow to
preserve Node’s fatal shutdown behavior for captured uncaughtException events:
record telemetry, perform only bounded cleanup, then invoke the original fatal
shutdown path rather than leaving the process running. Keep the existing handler
installation and cleanup tracking intact.
- Around line 290-291: Update the completion cleanup flow around
cleanupProcessExceptionTracking so it always runs even when await
flushTelemetry() rejects; move cleanup before the telemetry flush or wrap the
flush in try/finally. Apply the same exception-safe ordering or guarding to the
corresponding flow at line 353.

In `@packages/node/src/utils/telemetry.ts`:
- Around line 74-81: Update the uncaught-exception handler in the telemetry
setup to use uncaughtExceptionMonitor instead of uncaughtException, preserving
Node.js’s default stack-trace and exit behavior while still recording telemetry.
Update ProcessExceptionSource and all related tests and event assertions to use
the chosen monitor event, leaving unhandledRejection handling unchanged.
- Around line 47-64: Update normalizeTelemetryError and the shared
exception-capture flow so reported errors are sanitized before
span.recordException and Sentry capture. Build the exported Error from
allow-listed diagnostic fields or apply the established secret-pattern redaction
to message and stack values, including raw string exceptions, while preserving
the existing telemetryEnabled and active-span behavior.

In `@packages/node/src/utils/tool-observer.ts`:
- Around line 292-337: The telemetry calls in the completion handling path must
record exceptions on the captured activeSpan because finish occurs after
scope.run ends. Update recordTelemetryException usage around the exception and
returned_error branches to pass activeSpan or record directly on it before the
span ends, and add a regression test that asserts the exception event is present
on the span rather than only verifying the mocked recorder invocation.

In `@README.md`:
- Line 498: Update the HEVY_MCP_TELEMETRY row in README.md to clarify that
Sentry remains enabled for error monitoring only when telemetry is enabled and
SENTRY_DSN is non-empty; ensure the wording does not imply Sentry remains
enabled when HEVY_MCP_TELEMETRY is set to 0.

---

Nitpick comments:
In `@packages/node/src/index.test.ts`:
- Line 32: Update the test double for installProcessExceptionTracking in
testDoubles to retain the returned cleanup spy instead of discarding it. Add
lifecycle assertions verifying the cleanup spy runs during both successful
stdio/HTTP completion and failure paths, while keeping the tests passing with
the standard Vitest command excluding integration tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: eec3c352-4c70-40f9-8358-23fa1e8e2806

📥 Commits

Reviewing files that changed from the base of the PR and between 2cc78bf and aa57348.

📒 Files selected for processing (10)
  • .changeset/quiet-telemetry-opt-out.md
  • README.md
  • packages/core/src/observation.ts
  • packages/core/src/tools/tool-runtime.ts
  • packages/node/src/index.test.ts
  • packages/node/src/index.ts
  • packages/node/src/utils/telemetry.test.ts
  • packages/node/src/utils/telemetry.ts
  • packages/node/src/utils/tool-observer.test.ts
  • packages/node/src/utils/tool-observer.ts

Comment thread packages/node/src/index.ts Outdated
Comment thread packages/node/src/index.ts
Comment thread packages/node/src/utils/telemetry.ts Outdated
Comment thread packages/node/src/utils/telemetry.ts
Comment thread packages/node/src/utils/tool-observer.ts
Comment thread README.md Outdated
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Raw exception telemetry leak ✓ Resolved 🐞 Bug ⛨ Security
Description
ToolRuntime now includes the raw thrown error in ToolCompletionObservation.exception, and the
node tool observer prefers this value for telemetry exception recording, which can export unredacted
error messages/stacks and violates the existing “safe diagnostic” privacy contract.
Code

packages/core/src/tools/tool-runtime.ts[202]

+						exception: error,
Evidence
Core explicitly documents safe diagnostics as bounded and excluding raw messages/payloads/URLs, but
the new exception field carries the original thrown value and is used preferentially for
exception-event recording, which can export raw Error content.

packages/core/src/tools/tool-runtime.ts[195-205]
packages/node/src/utils/tool-observer.ts[289-315]
packages/core/src/utils/error-policy.ts[388-405]
packages/node/src/utils/tool-observer.test.ts[181-238]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR propagates raw thrown exceptions (`exception: error`) into tool completion observations and then records them via OpenTelemetry `recordException`. Raw `Error` objects can contain secrets (messages, stacks, embedded payloads/URLs), undermining the core privacy contract that telemetry uses bounded/safe diagnostics.
### Issue Context
Core already provides `SafeErrorDiagnostic` specifically to avoid raw messages/payloads/URLs. Current tool-observer tests assert that secrets from thrown errors must not appear in telemetry metadata, but the new exception plumbing bypasses that.
### Fix Focus Areas
- packages/core/src/tools/tool-runtime.ts[195-205]
- packages/node/src/utils/tool-observer.ts[289-337]
- packages/core/src/utils/error-policy.ts[388-405]
- packages/node/src/utils/tool-observer.test.ts[181-238]
### Suggested fix
- Do **not** record the raw thrown exception object.
- Build a sanitized `Error` for telemetry (e.g., `new Error(nextCompletion.error?.category ?? errorType ?? 'UnknownError')`) and optionally clear/avoid stack export, while attaching only safe attributes derived from `SafeErrorDiagnostic`.
- Alternatively: keep `exception` for internal control-flow but ensure `recordTelemetryException` always normalizes/redacts the message/stack based on `SafeErrorDiagnostic` before calling `span.recordException()`.
- Update/add tests asserting the raw thrown `Error('secret...')` does not get exported through telemetry exception recording.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Exception events dropped ✓ Resolved 🐞 Bug ◔ Observability
Description
recordTelemetryException() records against trace.getActiveSpan(), but createNodeToolObserver() calls
it from finish() after startActiveSpan() has completed, so tool failure exception events will
frequently be skipped because no active span exists.
Code

packages/node/src/utils/telemetry.ts[R54-65]

+export function recordTelemetryException(
+	error: unknown,
+	attributes?: Record<string, string | number | boolean>,
+): void {
+	if (!telemetryEnabled) return;
+	try {
+		const span = trace.getActiveSpan();
+		if (!span) return;
+		span.recordException(normalizeTelemetryError(error));
+		if (attributes) span.setAttributes(attributes);
+		span.setStatus({ code: SpanStatusCode.ERROR });
+	} catch {
Evidence
The helper records only when an active span exists, but the tool observer calls it from finish()
after the startActiveSpan callback has completed (and tests model that active-span context ends
when the callback resolves), so getActiveSpan() will often be absent at finish() time.

packages/node/src/utils/telemetry.ts[54-65]
packages/node/src/utils/tool-observer.ts[250-258]
packages/node/src/utils/tool-observer.ts[260-339]
packages/node/src/utils/tool-observer.test.ts[18-28]
packages/node/src/utils/tool-observer.test.ts[110-127]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`recordTelemetryException()` relies on `trace.getActiveSpan()`. In `createNodeToolObserver().finish()`, the tool span is stored in `activeSpan`, but the active context from `tracer.startActiveSpan()` has already unwound by the time `finish()` runs, so `getActiveSpan()` can be `undefined` and the exception event is dropped.
### Issue Context
The observer already has the correct span instance (`activeSpan`). Recording should target that span instead of relying on ambient context.
### Fix Focus Areas
- packages/node/src/utils/telemetry.ts[54-68]
- packages/node/src/utils/tool-observer.ts[250-339]
### Suggested fix
- Update `recordTelemetryException` to accept an optional `span` parameter (or export a `recordTelemetryExceptionOnSpan(span, ...)` helper).
- In `tool-observer.ts`, call the helper with `activeSpan` so exceptions reliably attach to the tool span even when it’s no longer the active span.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Import-time process listeners ✓ Resolved 🐞 Bug ☼ Reliability
Description
index.ts installs uncaughtException/unhandledRejection handlers at module import time, so
importing the package registers global process listeners and cleanup is skipped on early return
paths (e.g., --help/--version) and unavailable to library callers.
Code

packages/node/src/index.ts[33]

+const cleanupProcessExceptionTracking = installProcessExceptionTracking();
Evidence
The new code calls installProcessExceptionTracking() at module load, and the installer attaches
listeners immediately; early-return CLI branches exit without calling the cleanup function, and only
shutdown callbacks call cleanup.

packages/node/src/index.ts[32-35]
packages/node/src/index.ts[216-228]
packages/node/src/index.ts[283-292]
packages/node/src/index.ts[350-356]
packages/node/src/utils/telemetry.ts[70-88]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`installProcessExceptionTracking()` is invoked at module top-level in `index.ts`, registering process-wide listeners as an import side effect. Cleanup is only wired into certain shutdown paths and is not executed for early-return CLI paths.
### Issue Context
Import-time global listeners complicate test isolation, embedding as a library, and any scenario where the module is evaluated more than once (or where initialization is expected to be explicit).
### Fix Focus Areas
- packages/node/src/index.ts[32-35]
- packages/node/src/index.ts[216-228]
- packages/node/src/index.ts[283-356]
- packages/node/src/utils/telemetry.ts[70-88]
### Suggested fix
- Delay installation until the server is actually starting (after CLI action is determined to be `start`).
- Ensure cleanup runs for **all** exit paths where the module may remain loaded (including `help`/`version`, and any exported factory APIs if they’re intended for embedding).
- Consider returning a lifecycle object from `runServer`/`runStdioServer` or encapsulating startup/shutdown so the cleanup function is always invoked.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Telemetry docs contradict code ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
README now says HEVY_MCP_TELEMETRY=0 disables Sentry but also states “Sentry remains enabled”,
which contradicts the actual implementation where telemetryEnabled gates Sentry initialization
entirely.
Code

README.md[498]

+| `HEVY_MCP_TELEMETRY`         | Enabled                        | Local Node                    | Set to exactly `0` before startup/import to disable Sentry errors/traces and OTLP traces/metrics. Enabled OTLP traces include privacy-safe tool failure exception events and process-level exception events; Sentry remains enabled for error monitoring. |
Evidence
README claims Sentry remains enabled even while describing HEVY_MCP_TELEMETRY=0 as disabling
Sentry, but the code only calls Sentry.init() inside the if (telemetryEnabled) block controlled
by that env var.

README.md[492-501]
packages/node/src/utils/telemetry.ts[101-159]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The README entry for `HEVY_MCP_TELEMETRY` contains contradictory behavior claims, which can mislead users about whether Sentry is disabled or still active when telemetry is opted out.
### Issue Context
In code, `telemetryEnabled` (`HEVY_MCP_TELEMETRY !== '0'`) gates the entire telemetry initialization block, including `Sentry.init()`.
### Fix Focus Areas
- README.md[492-501]
- packages/node/src/utils/telemetry.ts[101-159]
### Suggested fix
- Update the README row to match implementation (e.g., `HEVY_MCP_TELEMETRY=0` disables both Sentry and OTLP exports).
- If the intended behavior is “disable OTLP but keep Sentry”, change the implementation to decouple those toggles and document the new behavior precisely.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread packages/node/src/utils/telemetry.ts
Comment thread packages/core/src/tools/tool-runtime.ts Outdated
Comment thread packages/node/src/index.ts Outdated
Comment thread README.md Outdated
@chrisdoc

chrisdoc commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Resolved in commit a4ce1c6:

  • Process tracking now uses uncaughtExceptionMonitor and starts a dedicated process exception span, preserving Node fatal behavior.
  • Tracking installs per server invocation and cleans up on startup failure and graceful shutdown.
  • Cleanup runs before telemetry flush.
  • Tool exception recording targets the captured tool span directly. Raw thrown exceptions are no longer propagated through the safe observation contract; telemetry uses bounded diagnostic errors.
  • Updated telemetry documentation and changeset wording.

Verification: npm run build; focused telemetry/index tests pass (29 tests).

@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 3.02kB (1.43%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
hevy-mcp-esm 214.04kB 3.02kB (1.43%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: hevy-mcp-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
src-BsXC3dOm.mjs (New) 211.87kB 211.87kB 100.0% 🚀
src-DBkGttYB.mjs (Deleted) -208.85kB 0 bytes -100.0% 🗑️

Files in src-BsXC3dOm.mjs:

  • ./src/index.ts → Total Size: 7.9kB

  • ./src/utils/tool-observer.ts → Total Size: 8.86kB

  • ./src/utils/telemetry.ts → Total Size: 4.85kB

@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 26.00000% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.36%. Comparing base (2cc78bf) to head (e4087e5).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/node/src/utils/telemetry.ts 0.00% 28 Missing ⚠️
packages/node/src/utils/tool-observer.ts 50.00% 0 Missing and 8 partials ⚠️
packages/node/src/index.ts 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #839      +/-   ##
==========================================
- Coverage   80.08%   79.36%   -0.73%     
==========================================
  Files          67       67              
  Lines        3686     3736      +50     
  Branches     1037     1058      +21     
==========================================
+ Hits         2952     2965      +13     
- Misses        383      412      +29     
- Partials      351      359       +8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@mergify

mergify Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/quiet-telemetry-opt-out.md:
- Line 5: Update the changeset wording to use the compound modifier “tool-thrown
errors” and explicitly name uncaughtExceptionMonitor and unhandledRejection as
process event telemetry sources.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9aac95f7-88b6-4015-978a-c7e1ef5fc156

📥 Commits

Reviewing files that changed from the base of the PR and between aa57348 and e4087e5.

⛔ Files ignored due to path filters (1)
  • hk.pkl is excluded by !**/*.pkl
📒 Files selected for processing (12)
  • .changeset/quiet-telemetry-opt-out.md
  • .changeset/tidy-cli-smoke.md
  • .dockerignore
  • AGENTS.md
  • Dockerfile
  • README.md
  • package.json
  • packages/cli/tests/npm-pack-smoke.mjs
  • packages/node/src/index.ts
  • packages/node/src/utils/telemetry.ts
  • packages/node/src/utils/tool-observer.ts
  • scripts/install-git-hooks.mjs
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/node/src/utils/tool-observer.ts
  • packages/node/src/index.ts
  • packages/node/src/utils/telemetry.ts
  • README.md

"hevy-mcp": patch
---

Record tool thrown-error and returned-error exceptions as OpenTelemetry span exception events; add process-level uncaughtExceptionMonitor and unhandledRejection telemetry tracking.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify the compound modifiers in the changeset.

Use “tool-thrown errors” instead of “tool thrown-error”. Also identify the process event names as telemetry sources.

Suggested wording
-Record tool thrown-error and returned-error exceptions as OpenTelemetry span exception events; add process-level uncaughtExceptionMonitor and unhandledRejection telemetry tracking.
+Record exceptions for tool-thrown errors and returned tool errors as OpenTelemetry span exception events. Add process-level telemetry tracking for `uncaughtExceptionMonitor` and `unhandledRejection`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Record tool thrown-error and returned-error exceptions as OpenTelemetry span exception events; add process-level uncaughtExceptionMonitor and unhandledRejection telemetry tracking.
Record exceptions for tool-thrown errors and returned tool errors as OpenTelemetry span exception events. Add process-level telemetry tracking for `uncaughtExceptionMonitor` and `unhandledRejection`.
🧰 Tools
🪛 LanguageTool

[grammar] ~5-~5: Use a hyphen to join words.
Context: --- "hevy-mcp": patch --- Record tool thrown-error and returned-error exceptio...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/quiet-telemetry-opt-out.md at line 5, Update the changeset
wording to use the compound modifier “tool-thrown errors” and explicitly name
uncaughtExceptionMonitor and unhandledRejection as process event telemetry
sources.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant