Skip to content

feat(otel): tag signals with enduser.id for multi-developer attribution#52

Open
danielemoraschi wants to merge 2 commits into
DEVtheOPS:mainfrom
danielemoraschi:feat/enduser-id
Open

feat(otel): tag signals with enduser.id for multi-developer attribution#52
danielemoraschi wants to merge 2 commits into
DEVtheOPS:mainfrom
danielemoraschi:feat/enduser-id

Conversation

@danielemoraschi
Copy link
Copy Markdown

@danielemoraschi danielemoraschi commented May 16, 2026

Description

Adds an enduser.id attribute to every metric datapoint, log record, and trace span (both the resource and per-span attributes) so telemetry exported to a shared OTLP backend can be attributed to the developer who generated it. The value is auto-detected from os.userInfo().username and refined from opencode's cfg.username config field once the config hook fires. Set OPENCODE_DISABLE_USER_TRACKING to any non-empty value to opt out.

enduser.id is added at the signal level (datapoint / record / span attributes), not just on the OTel resource, because Datadog's direct OTLP intake (otlp.datadoghq.com / otlp.datadoghq.eu) promotes only a hardcoded set of well-known resource attributes to tags and silently drops the rest — so resource-only attribution shows up as "no tag" in the Datadog Metrics Summary. Datapoint-level attributes are always preserved across backends. Override precedence is unchanged: OPENCODE_RESOURCE_ATTRIBUTES=enduser.id=… still wins on the resource. When os.userInfo() throws (e.g. containerised runs with no passwd entry), the attribute is omitted entirely rather than tagged with a placeholder like "unknown".

See the new "User identity tracking" section in README.md for the full propagation table and opt-out details.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Chore (dependency updates, etc.)

Checklist

  • I have read the CONTRIBUTING.md document
  • My code follows the style guidelines of this project
  • bun run lint passes with no errors
  • bun run check:jsdoc-coverage passes with no errors (85.11%, 40/47)
  • bun run typecheck passes with no errors
  • bun test passes with no errors (251 pass / 0 fail)
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the documentation accordingly
  • My commits follow the Conventional Commits specification

Related issues

None.

Additional context

Tests added (8 total):

  • tests/config.test.tsdisableUserTracking defaults to false; becomes true on any non-empty env value.
  • tests/otel.test.tsbuildResource(version, endUserId) includes / omits enduser.id; OTEL_RESOURCE_ATTRIBUTES=enduser.id=… overrides the seeded value.
  • tests/util.test.tssafeUsername() returns the OS username on success and undefined when os.userInfo() throws.

End-to-end verification suggested before merge (not covered by unit tests):

  • Run against a local collector with OPENCODE_ENABLE_TELEMETRY=1 and confirm enduser.id=<OS user> appears on a metric datapoint, a log record, and a span resource.
  • Set OPENCODE_DISABLE_USER_TRACKING=1 and confirm the attribute is absent on all three signals.
  • Set OPENCODE_RESOURCE_ATTRIBUTES=enduser.id=override and confirm the resource shows override while signals show the OS user.
  • Set username: "bob" in opencode config and confirm metrics / logs flip to bob after the config hook fires; the trace resource remains the OS user (documented quirk).

Summary by CodeRabbit

  • New Features

    • User identity tracking now available in metrics, logs, and traces when enabled
    • Added OPENCODE_DISABLE_USER_TRACKING environment variable to opt out
  • Documentation

    • Added "User identity tracking" section explaining how user identity is derived and configured
    • Updated environment variables reference with new tracking configuration option

Review Change Stack

Auto-detect the OS username via os.userInfo() and tag every metric
datapoint, log record, and trace span (resource and span attributes)
with `enduser.id`. Refine from opencode's `cfg.username` once the
config hook fires.

Signal-level attribution (not resource-only) is required because
Datadog's direct OTLP intake promotes only a hardcoded set of resource
attributes to tags and silently drops the rest. OTEL_RESOURCE_ATTRIBUTES
overrides still win on the resource. Opt out with
OPENCODE_DISABLE_USER_TRACKING. When os.userInfo() throws, the attribute
is omitted entirely rather than tagged with a placeholder.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

Warning

Rate limit exceeded

@danielemoraschi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 54 minutes and 1 second before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a755815b-f330-41f7-8867-478e779cff53

📥 Commits

Reviewing files that changed from the base of the PR and between 2a38e7d and c6a8a19.

📒 Files selected for processing (1)
  • tests/util.test.ts
📝 Walkthrough

Walkthrough

This PR adds optional user identity tracking to OpenTelemetry instrumentation by deriving enduser.id from the OS username and threading it through OTel resource initialization. User tracking is enabled by default but can be disabled via the OPENCODE_DISABLE_USER_TRACKING environment variable. The feature includes configuration parsing, safe username detection with error handling, OTel signature extensions, plugin integration with runtime config updates, and comprehensive documentation.

Changes

User identity tracking feature

Layer / File(s) Summary
Type contracts and configuration
src/types.ts, src/config.ts, tests/config.test.ts
CommonAttrs extended with optional enduser.id attribute; MutableCommonAttrs introduced as writable variant. PluginConfig gains disableUserTracking: boolean field populated from OPENCODE_DISABLE_USER_TRACKING environment variable. Tests verify defaults and enable/disable behavior via "1" and non-empty values.
Safe username detection utility
src/util.ts, tests/util.test.ts
New safeUsername() exported function wraps os.userInfo() in try/catch and returns username string or undefined on error. Tests verify successful retrieval and undefined return when os.userInfo throws in containerized or restricted environments.
OTel initialization signatures
src/otel.ts, tests/otel.test.ts
buildResource(version, endUserId?) and setupOtel(..., endUserId?) accept optional endUserId parameter. Resource attributes conditionally include enduser.id when provided; OTEL_RESOURCE_ATTRIBUTES takes precedence. Tests verify inclusion/omission and override behavior.
Plugin initialization and runtime integration
src/index.ts
Plugin computes endUserId from safeUsername() when tracking enabled, threads it to setupOtel(), and initializes mutable commonAttrs with conditional enduser.id. Config update handler mutates commonAttrs["enduser.id"] when tracking enabled and non-empty username provided.
Documentation and environment reference
AGENTS.md, README.md
AGENTS.md adds OPENCODE_DISABLE_USER_TRACKING to required env var list. README.md adds configuration table entry and new "User identity tracking" section explaining enduser.id derivation, precedence with OTEL_RESOURCE_ATTRIBUTES, failure handling, and opt-out mechanism.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested reviewers

  • dialupdisaster
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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 summarizes the main change: adding enduser.id tagging to OpenTelemetry signals for multi-developer attribution, which is the primary objective of this changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 `@tests/util.test.ts`:
- Around line 12-16: Make the success-path test for safeUsername deterministic
by mocking os.userInfo in the test: use a jest spy/mock on os.userInfo to return
an object with a username (e.g. "testuser") before calling safeUsername in the
"returns the OS username on success" test, assert against the returned string as
now, and restore/clear the mock after the test so other tests are unaffected;
reference the safeUsername helper and the test in tests/util.test.ts when adding
the mock.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 65790093-68dc-4975-8602-4ee740a6c2ec

📥 Commits

Reviewing files that changed from the base of the PR and between a123a37 and 2a38e7d.

📒 Files selected for processing (10)
  • AGENTS.md
  • README.md
  • src/config.ts
  • src/index.ts
  • src/otel.ts
  • src/types.ts
  • src/util.ts
  • tests/config.test.ts
  • tests/otel.test.ts
  • tests/util.test.ts

Comment thread tests/util.test.ts
The original test called the real `os.userInfo()` and asserted on its
return, which fails in minimal containers (e.g. distroless / Alpine
without a passwd entry) where `os.userInfo()` throws and `safeUsername`
returns `undefined`. Mock the success case so the test is deterministic
across environments, matching the pattern already used by the error-path
test.
@dialupdisaster
Copy link
Copy Markdown
Contributor

I like the problem this is solving, especially for shared backends where per-developer attribution is genuinely useful.

My hesitation is mostly about hardcoding enduser.id as a special case in the plugin. This feels like the first of several backend-compatibility requests we could end up getting for individual attributes, and I’m a little wary of starting down a path of one-off shims.

Would it make more sense to generalize this slightly and make the behavior resource-attribute driven instead? Something along the lines of:

OPENCODE_PROPAGATE_RESOURCE_ATTRIBUTES=enduser.id

The idea would be:

  1. OPENCODE_RESOURCE_ATTRIBUTES remains the source of truth for resource metadata.
  2. OPENCODE_PROPAGATE_RESOURCE_ATTRIBUTES lists resource attribute keys that should also be copied onto emitted metric datapoints, log records, and spans.
  3. This keeps the model OpenTelemetry-shaped and avoids introducing backend-specific “tags” config or special handling for just one attribute.

That seems like it would still solve the Datadog-style compatibility issue, while keeping the plugin more general-purpose and avoiding a growing list of bespoke propagated fields later.

I’d strongly prefer that we not introduce single hardcoded propagated attributes into the plugin, since I think that sets us up for more one-off exceptions over time. If you’d rather not take that on in this PR, I’d be perfectly happy for this one to be closed and for us to tackle the generalized version instead.

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