Imported from DataDog/dd-trace-rb (
AGENTS.md). Install upstream withnpx skills add DataDog/dd-trace-rb. Copyright stays with the author.
This repository is the source code of a Ruby gem created by Datadog to provide Distributed Tracing (APM), Profiling, App & API Protection (AppSec), Dynamic Instrumentation (DI, Live Debugger), Data Streams Monitoring (DSM), Error Tracking, OpenTelemetry, and OpenFeature to Ruby applications.
Setup & Quick Commands
Ruby version compatibility: Ruby 2.5+ (including 3.x+ and 4.x+)
- Launch MRI container:
docker compose run --rm tracer-4.0 /bin/bash. Matches CI defaults. Other Ruby versions and variants are indocker-compose.yml. - Install dependencies:
bundle install. Run once per container/session. - Discover gemfiles:
bundle exec rake dependency:list. Shows values forBUNDLE_GEMFILE. - Use an alternate gemfile for matrix-specific jobs:
BUNDLE_GEMFILE=$(pwd)/gemfiles/<name>.gemfile. - Smoke verification:
bundle exec rake test:main. Baseline general testing (no native or integration testing). - Lint and type check:
bundle exec rake rubocop typecheck. Prefer RuboCop because it checks a strict superset of the Standard rules; CI requires both, which can be run withbundle exec rake standard rubocop typecheck. - Type check specific sources:
bundle exec steep check [sources]. - Discover tasks:
bundle exec rake -T. - Run targeted specs:
bundle exec rspec spec/path/to/file_spec.rb[:line]. Only use this for specs covered bytest:mainor underspec/datadog/profiling; use the relevant rake task for other specs. - Compile native extensions:
bundle exec rake compileorbundle exec rake clean compile. Seedocs/ProfilingDevelopment.mdanddocs/LibdatadogDevelopment.md.
Project Structure
lib/- Ruby code that's shipped by this gemext/- Native code that's shipped by this gemsig/- RBS signatures maintained with Steepspec/- RSpec suites mirroringlib/Matrixfile,appraisal/- Test matrix gemset specificationgemfiles/- Generated gemfiles from the matrix (no direct editing).github,tasks/github.rake,.gitlab-ci.yml,.gitlab- CIlib/datadog/appsec- app & api protection implementation (formerly known as appsec)lib/datadog/appsec/contrib- app & api protection integrations with third-party librarieslib/datadog/core- product-agnostic glue and shared codelib/datadog/error_tracking- error trackinglib/datadog/kit- shared product featureslib/datadog/data_streams- Data Streams Monitoringlib/datadog/di- dynamic instrumentation (docs/DynamicInstrumentation.md)lib/datadog/open_feature- an implementation of OpenFeature Provider https://openfeature.dev/docs/reference/sdks/server/ruby. Before modifying OpenFeature code, specs, or signatures, read and followlib/datadog/open_feature/AGENTS.md.lib/datadog/opentelemetry- support OpenTelemetry API for tracing and metrics (docs/OpenTelemetry.md)lib/datadog/profiling- profilinglib/datadog/tracing- distributed tracinglib/datadog/tracing/contrib- distributed tracing integrations with third-party librariesext/datadog_profiling_native_extension- C extension for profilingext/libdatadog_api- C bindings for the Rust libdatadog librarydocs/- Authoritative developer guides. Includes API documentation, upgrade guides, etc.
Noteworthy paths
lib/datadog.rb- Gem entry pointlib/datadog/auto_instrument.rb,**/preload.rb- Alternative gem entry points (docs/AutoInstrumentation.md)lib/datadog/core/configuration/components.rblib/datadog/*/component.rb- global gem wiring and initialization**/settings.rb- user configuration definition**/ext.rb- constants for each subsystemlib/datadog/core/telemetry/- self telemetry for this gem (docs/TelemetryDevelopment.md)
Integration pattern
Each framework integration (lib/datadog/*/contrib/) follows a common pattern:
patcher.rb- Modifies framework behaviorintegration.rb- Describes the integrationext.rb- Constants specific to the integrationconfiguration/settings.rb- Integration-specific settings
One-Pipeline (GitLab CI)
The GitLab CI configuration (.gitlab-ci.yml) includes a remote template called
"one-pipeline" via .gitlab/one-pipeline.locked.yml. This template defines OCI
packaging, lib-injection image building, and promotion jobs shared across all Datadog
tracing libraries.
- Source repo:
DataDog/libdatadog-buildon GitHub (templates/one-pipeline.yml) - Distribution: A GitLab CI job publishes the template to
gitlab-templates.ddbuild.iounder a content-addressed hash. A campaigner tool then opens PRs (titled "chore(ci) update one-pipeline") in all consuming repos to update the locked URL in.gitlab/one-pipeline.locked.yml. - Local overrides:
.gitlab-ci.ymloverrides template variables likeOCI_PACKAGE_MAX_SIZE_BYTESandLIB_INJECTION_IMAGE_MAX_SIZE_BYTES. Whenpackage-ocijobs fail with size limit errors, check the local override values in.gitlab-ci.yml- the template's error messages hardcode the default limit, not the actual override value. - Consuming repos: dd-trace-rb, dd-trace-java, dd-trace-py, dd-trace-dotnet,
dd-trace-js, dd-trace-php, auto_inject, httpd-datadog, nginx-datadog,
inject-browser-sdk (listed in
libdatadog-build/campaigner-config.yml).
images-rb pin updates
.github/workflows/update-images.yml receives a repository_dispatch from
images-rb (after its main builds successfully) and opens a PR pinning this
repo to the new images. images-rb authenticates via the
images-rb.notify-consumers dd-octo-sts trust policy
(.github/chainguard/images-rb.notify-consumers.sts.yaml), an in-repo file -
no external grant needed. No local trigger otherwise.
Guidelines
Ask First
- Modifying dependencies in
datadog.gemspec,appraisal/, orMatrixfile - Editing CI workflows or release automation
- Touching vendored third-party code (except
vendor/rbs) - Storing sensitive data or PII in data structures, passing it as function arguments, or logging it
- Modifying
@public_apiannotated code or making backwards-compatible public API changes; readdocs/PublicApi.mdfirst
Never
- Use
git commit --amendunless the user explicitly and clearly requests it; create a new commit by default - Push commits to a remote unless the user explicitly requests it
- Commit secrets, tokens, or credentials
- Edit files under
gemfiles/; regenerate them withbundle exec rake dependency:generate - Change versioning (
lib/datadog/version.rb,CHANGELOG.md) - Leave resources open; terminate threads and close files
- Make breaking public API changes
- Use
sleepin tests for synchronization; use deterministic waits such asQueue,ConditionVariable, blocking flush methods, or mocked time
Code changes
- Follow the
write-commentskill (.agents/skills/write-comment/SKILL.md) for when a comment earns its place; default to no comment otherwise. - Use
Core::Utils::EnumerableCompat.filter_mapinstead offilter_mapfor compatibility with Ruby 2.5 and 2.6 (nativefilter_maprequires Ruby 2.7+). - Use
Datadog::Core::Utils::Time.nowinstead ofTime.noweverywhere. The time provider is configurable (for example, for Timecop support), and tests can override it viaCore::Utils::Time.now_provider=.
Documentation
- Never mention telemetry in customer-facing Dynamic Instrumentation documentation such as
docs/DynamicInstrumentation.md. Telemetry is internal and inaccessible to customers; only mention observable behavior, while internal code comments may describe telemetry. - All user-facing product documentation lives in
docs/GettingStarted.md; update it when adding user-facing settings or environment variables.
Environment variables
- Use
DATADOG_ENV, neverENVdirectly (seedocs/AccessEnvironmentVariables.md). - Run
rake local_config_map:generatewhen adding new environment variables.
Testing
Matrixfile defines testing combinations, and appraisal/ files declare their gemsets. Generated gemfiles live under gemfiles/. The Matrixfile and Rakefile are authoritative.
Always use rake tasks
Tests must be run via bundle exec rake test:TASK_KEY, not bare bundle exec rspec, because most suites require specific Gemfiles and the rake task selects the correct one. The test:main task uses the default Gemfile; its specs and specs under spec/datadog/profiling may be run directly with bundle exec rspec.
Finding the right rake task
- Identify the component from the changed path under
lib/datadog/orspec/datadog/(for example,appsec,profiling,redis, orsinatra). - Search with
bundle exec rake -T test | grep KEYWORDusing the component name. - Check the Rakefile
spec:TASKdefinition for included and excluded specs, and checkMatrixfilefor Ruby version compatibility.
Docker
- AppSec integration tests need Ruby 3.3. Use Ruby 3.3 installed locally or
docker compose run --rm tracer-3.3 /bin/bash, then run the rake task inside. test:mainandbundle exec rspec spec/datadog/profilingcan run locally on any Ruby for quick feedback.- If Bundler fails inside the container after a dependency update, run
bundle installand retry the rake task once before investigating further.
Verifying across Ruby versions
Before marking a task complete, run the relevant test task on the earliest and latest Ruby versions supported by its Matrixfile entry. Skip unsupported versions.
# If mise is available (use 2.6 if 2.5 is unavailable; 2.5 no longer builds on macOS):
mise exec ruby@2.6 -- bundle exec rake test:TASK_KEY
mise exec ruby@4.0 -- bundle exec rake test:TASK_KEY
# Otherwise, use Docker:
docker compose run --rm tracer-2.5 bundle exec rake test:TASK_KEY
docker compose run --rm tracer-4.0 bundle exec rake test:TASK_KEY
Pull Requests
- Push branches to
DataDog/dd-trace-rb, not forks. - Use
--repo DataDog/dd-trace-rbwithghcommands; defaults are unreliable. - Use
.github/PULL_REQUEST_TEMPLATE.mdas the starting point for PR descriptions. - Write concisely for the developer performing code review, using one sentence per relevant summary or motivation point.
- Write changelog entries for customers. Customer-visible changes need a changelog fragment in
unreleased/; write it with the write-changelog skill (.agents/skills/write-changelog/). Internal CI, tooling, and tracer telemetry consumed only by Datadog engineering need no fragment. - Telemetry that powers customer-facing Datadog product features, such as DI autocomplete, profiling, or AppSec, needs a customer-facing changelog fragment even though its data flows through the Datadog backend.
- Add
--label "AI Generated"when creating PRs; the label is sufficient, so do not mention AI in the description.
GitHub Actions
When creating or modifying workflows in .github/workflows/:
Security
-
Never interpolate user input directly in
run:blocks; useenv:instead:# BAD: run: echo "${{ github.event.comment.body }}" # GOOD: env: COMMENT: ${{ github.event.comment.body }} run: echo "$COMMENT" -
User-controllable inputs include
github.event.comment.body,github.event.issue.title,github.event.pull_request.title, andgithub.head_ref. -
Pin actions to a SHA:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2. -
Set
permissions: {}at workflow level and explicit minimal permissions per job. -
Prefer
pull_requestoverpull_request_target.
Shell scripts
- Always quote variables:
"$VAR", not$VAR. - Quote
$GITHUB_OUTPUT:echo "key=value" >> "$GITHUB_OUTPUT". - Group multiple redirects:
{ echo "a"; echo "b"; } >> "$GITHUB_OUTPUT". - Avoid heredocs; use echo grouping instead.
Validation
yamllint --strict .github/workflows/your-workflow.yml
actionlint .github/workflows/your-workflow.yml
Style
StandardRB enforces style: bundle exec rake standard:fix.
Additional team preferences:
- Use trailing commas in multi-line arrays, hashes, and arguments.
- Mirror the
lib/structure in RBS definitions undersig/. - Use
Type?over(nil | Type). - Type a value with its specific concrete type when it has one, rather than
untypedorany. - Use a generic type parameter to preserve an input/output relationship (for example,
[T < Object] (T item) -> T) rather thanuntypedorany;anyis the fallback for genuinely unconstrained values, not a substitute for a generic. - Use
anyonly when every possible type is intentionally valid and the code does not depend on the value's concrete type. If the type is merely unknown or not yet modelled, useuntyped.
Ruby idioms:
- Prefer
x.to_soverx || ''for nil-safe string conversion. - Prefer
return unless xoverreturn nil unless x(implicit nil). - Prefix unused method arguments with
_(for example,_unused) or use**_optsfor intentionally ignored keyword arguments.
Gotchas
- Pipe
rspecandrake test:*output through2>&1 | tee /tmp/full_rspec.log | grep -E 'Pending:|Failures:|Finished' -A 99for concise but complete results. - Thread leaks: use
rspec --seed <N>and inspectdocs/DevelopmentGuide.md#ensuring-tests-dont-leak-resources. docker compose runfailures: rundocker compose pullbefore retrying.ProbeNotifierWorker#flushblocks until queues are empty; never addsleepafter it.
Skills
Skills live under .agents/skills/ and are harness-agnostic. Read them before the matching task:
- Before editing
sig/**/*.rbs,vendor/rbs/**, or any inline#:annotation:.agents/skills/write-rbs/SKILL.md - Before writing any code comment:
.agents/skills/write-comment/SKILL.md - Before writing a changelog fragment:
.agents/skills/write-changelog/SKILL.md
.claude/ holds only Claude Code registration: settings.json (hook wiring) and hooks/, a Claude-only hard guard enforcing the first two skills above. Other harnesses rely on the pointers in this section.
References
docs/DevelopmentGuide.md- detailed development workflowsdocs/GettingStarted.md- user-facing documentationdocs/StaticTypingGuide.md- RBS and Steep usagedocs/PublicApi.md- public API guidelines
How to Use This File
- This file is the source of truth for repository-wide agent guidance;
CLAUDE.mdimports it and should not duplicate it. - Read files before editing them.
- When the user says "suggest" or asks a question, analyze only; do not modify code.
- When the user says "fix", "change", or "update", make the changes.
- If a requested change contradicts code evidence, alert the user before proceeding.
- If a requested web page is inaccessible, state this and explain the basis for any suggestions.
- Read the specialized personas under
.cursor/rules/when writing code (code-style.mdc) or tests (testing.mdc). - Agent skills live under
.agents/skills/and are harness-agnostic;.claude/holds Claude Code registration only. See.claude/hooks/README.mdfor the hook build, test, and native re-verification workflow. - This
AGENTS.mdis a living document; update it when CI or scripts evolve, and update specialized personas as appropriate.
