From bb41d7c1fd13ffd0cdf11726526067c6bab4140a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:19:47 +0000 Subject: [PATCH 1/6] chore(deps): bump the gh-actions-packages group across 2 directories with 3 updates (#7560) Bumps the gh-actions-packages group with 3 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [DataDog/junit-upload-github-action](https://github.com/datadog/junit-upload-github-action) and [actions/stale](https://github.com/actions/stale). Bumps the gh-actions-packages group with 3 updates in the /.github/workflows directory: [actions/checkout](https://github.com/actions/checkout), [DataDog/junit-upload-github-action](https://github.com/datadog/junit-upload-github-action) and [actions/stale](https://github.com/actions/stale). Updates `actions/checkout` from 6.0.1 to 6.0.2 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6.0.1...de0fac2e4500dabe0009e67214ff5f5447ce83dd) Updates `DataDog/junit-upload-github-action` from 2.0.0 to 2.1.1 - [Release notes](https://github.com/datadog/junit-upload-github-action/releases) - [Commits](https://github.com/datadog/junit-upload-github-action/compare/v2...055560f63c405095e9228ba443eee7987e22bb94) Updates `actions/stale` from 10.1.1 to 10.2.0 - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/997185467fa4f803885201cee163a9f38240193d...b5d41d4e1d5dceea10e7104786b73624c18a190f) Updates `actions/checkout` from 6.0.1 to 6.0.2 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6.0.1...de0fac2e4500dabe0009e67214ff5f5447ce83dd) Updates `DataDog/junit-upload-github-action` from 2.0.0 to 2.1.1 - [Release notes](https://github.com/datadog/junit-upload-github-action/releases) - [Commits](https://github.com/datadog/junit-upload-github-action/compare/v2...055560f63c405095e9228ba443eee7987e22bb94) Updates `actions/stale` from 10.1.1 to 10.2.0 - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/997185467fa4f803885201cee163a9f38240193d...b5d41d4e1d5dceea10e7104786b73624c18a190f) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: gh-actions-packages - dependency-name: DataDog/junit-upload-github-action dependency-version: 2.1.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: gh-actions-packages - dependency-name: actions/stale dependency-version: 10.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: gh-actions-packages - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: gh-actions-packages - dependency-name: DataDog/junit-upload-github-action dependency-version: 2.1.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: gh-actions-packages - dependency-name: actions/stale dependency-version: 10.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: gh-actions-packages ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/appsec.yml | 4 ++-- .github/workflows/stale.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/appsec.yml b/.github/workflows/appsec.yml index 595a08f3d08..9755a40b77c 100644 --- a/.github/workflows/appsec.yml +++ b/.github/workflows/appsec.yml @@ -520,14 +520,14 @@ jobs: env: PLUGINS: stripe steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/node/oldest-maintenance-lts - uses: ./.github/actions/install - run: yarn test:appsec:plugins:ci - uses: ./.github/actions/node/latest - run: yarn test:appsec:plugins:ci - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 - - uses: DataDog/junit-upload-github-action@762867566348d59ac9bcf479ebb4ec040db8940a # v2.0.0 + - uses: DataDog/junit-upload-github-action@055560f63c405095e9228ba443eee7987e22bb94 # v2.1.1 if: always() && github.actor != 'dependabot[bot]' with: api_key: ${{ secrets.DD_API_KEY }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4dd7a5683a2..6a63a43c84e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,7 +10,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: days-before-issue-stale: -1 # disabled for issues From 3f0b58215c446c76f041839f0c21bd2174258b0c Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 18 Feb 2026 15:12:38 +0100 Subject: [PATCH 2/6] chore: configure Datadog merge queue (#7561) Add `repository.datadog.yaml` configuration to enable the Datadog merge queue with speculative workflow. This allows PRs to be validated against the latest base branch before merging, preventing conflicts and broken builds in high-velocity merges. The queue is restricted to `master` to avoid running on other branches. Configuration details: - Speculative workflow with fallback enabled - Up to 5 PRs validated in parallel - GitLab checks enabled with retry support - Fail-fast disabled to wait for full pipeline completion - Labels skipped to avoid triggering required status checks --- repository.datadog.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 repository.datadog.yaml diff --git a/repository.datadog.yaml b/repository.datadog.yaml new file mode 100644 index 00000000000..9a2fac753ca --- /dev/null +++ b/repository.datadog.yaml @@ -0,0 +1,23 @@ +--- +schema-version: v1 +kind: mergequeue + +enable: false + +branches: + master: + enable: true + +# PRs A, B -> A, A+B workflows being generated +workflow_type: speculative +merge_method: squash +gitlab_check_enable: true +gitlab_jobs_retry_enable: true +# Wait for the full pipeline to complete before checking the status (we have some jobs which auto-retry) +gitlab_fail_fast: false +# Do not add merge queue status labels on the PR, they will trigger required status checks and block the PR from the MQ +skip_labels: true +# Validate up to 5 PRs from the queue at once +speculative_max_depth: 5 +# Run CI for A, A+B, and B such that if A or A+B fail, but B passes, we can still merge B +speculative_fallback_enable: true From dfd8e8f161575cd4ad1a177ea3dadc5836eefc4c Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 18 Feb 2026 16:37:25 +0100 Subject: [PATCH 3/6] ci: fix Datadog merge queue config filename (#7561) (#7562) Move merge queue settings into `repository.datadog.yml` and remove `repository.datadog.yaml`. PR #7561 added the config under the wrong extension even though the `.yml` file already existed. --- repository.datadog.yaml | 23 ----------------------- repository.datadog.yml | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 23 deletions(-) delete mode 100644 repository.datadog.yaml diff --git a/repository.datadog.yaml b/repository.datadog.yaml deleted file mode 100644 index 9a2fac753ca..00000000000 --- a/repository.datadog.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- -schema-version: v1 -kind: mergequeue - -enable: false - -branches: - master: - enable: true - -# PRs A, B -> A, A+B workflows being generated -workflow_type: speculative -merge_method: squash -gitlab_check_enable: true -gitlab_jobs_retry_enable: true -# Wait for the full pipeline to complete before checking the status (we have some jobs which auto-retry) -gitlab_fail_fast: false -# Do not add merge queue status labels on the PR, they will trigger required status checks and block the PR from the MQ -skip_labels: true -# Validate up to 5 PRs from the queue at once -speculative_max_depth: 5 -# Run CI for A, A+B, and B such that if A or A+B fail, but B passes, we can still merge B -speculative_fallback_enable: true diff --git a/repository.datadog.yml b/repository.datadog.yml index ded5018823b..9a2fac753ca 100644 --- a/repository.datadog.yml +++ b/repository.datadog.yml @@ -1,4 +1,23 @@ --- schema-version: v1 kind: mergequeue + enable: false + +branches: + master: + enable: true + +# PRs A, B -> A, A+B workflows being generated +workflow_type: speculative +merge_method: squash +gitlab_check_enable: true +gitlab_jobs_retry_enable: true +# Wait for the full pipeline to complete before checking the status (we have some jobs which auto-retry) +gitlab_fail_fast: false +# Do not add merge queue status labels on the PR, they will trigger required status checks and block the PR from the MQ +skip_labels: true +# Validate up to 5 PRs from the queue at once +speculative_max_depth: 5 +# Run CI for A, A+B, and B such that if A or A+B fail, but B passes, we can still merge B +speculative_fallback_enable: true From 9dc65a7a2e45e79e8391d184d8c6bd4955ad0254 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 18 Feb 2026 17:31:57 +0100 Subject: [PATCH 4/6] chore(cursor): add worktrees.json for worktree setup (#7563) chore(cursor): add worktrees.json for worktree setup Run `yarn install` when Cursor creates a worktree so parallel agent worktrees have dependencies installed. Merge branch 'master' into watson/cursor-worktrees Co-authored-by: thomas.watson --- .cursor/worktrees.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .cursor/worktrees.json diff --git a/.cursor/worktrees.json b/.cursor/worktrees.json new file mode 100644 index 00000000000..d9cf01692f1 --- /dev/null +++ b/.cursor/worktrees.json @@ -0,0 +1,5 @@ +{ + "scripts": { + "postCreate": "yarn install" + } +} From 12ef29f31b25e89a49d3b0cdded67d7d81ee9b7b Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 18 Feb 2026 18:12:27 +0100 Subject: [PATCH 5/6] chore: add add-new-instrumentation agent skill (#7564) chore: add add-new-instrumentation agent skill Adds a shared skill guide for creating new dd-trace instrumentations and plugins. The skill lives under `.agents/skills/` and is symlinked into both `.claude/skills/` and `.cursor/skills/` so it's available to all AI agents. Updates `.gitignore` to track the skill files while continuing to exclude other Claude/Cursor settings. Address review comments Co-authored-by: thomas.watson --- .../skills/add-new-instrumentation/SKILL.md | 369 ++++++++++++++++++ .claude/skills/add-new-instrumentation | 1 + .cursor/skills/add-new-instrumentation | 1 + .gitignore | 4 +- 4 files changed, 373 insertions(+), 2 deletions(-) create mode 100644 .agents/skills/add-new-instrumentation/SKILL.md create mode 120000 .claude/skills/add-new-instrumentation create mode 120000 .cursor/skills/add-new-instrumentation diff --git a/.agents/skills/add-new-instrumentation/SKILL.md b/.agents/skills/add-new-instrumentation/SKILL.md new file mode 100644 index 00000000000..15d03004667 --- /dev/null +++ b/.agents/skills/add-new-instrumentation/SKILL.md @@ -0,0 +1,369 @@ +--- +name: add-new-instrumentation +description: Guide for adding new instrumentation and plugins to dd-trace. Use when creating a new plugin, adding instrumentation for a third-party library, or when the user asks about adding new instrumentations or plugins. +--- + +# Adding New Instrumentation + +## Architecture Overview + +The instrumentation system has two layers that communicate via Node.js diagnostic channels: + +1. **Instrumentation** (`packages/datadog-instrumentations/src/.js`) — hooks into third-party library internals using `addHook()` and `shimmer`, then publishes events to named diagnostic channels. +2. **Plugin** (`packages/datadog-plugin-/src/index.js`) — subscribes to those channels to implement APM tracing logic (spans, metadata, errors). + +This separation means you almost always need to create **both** files. + +## Step 1: Create the Instrumentation File + +Create `packages/datadog-instrumentations/src/.js`. The following is a starting-point template — adapt the wrapped method(s), context fields, and channel operations to match the actual library's API. Read 1-2 existing instrumentations for the library type you're adding (e.g. `kafkajs.js` for messaging, `redis.js` for caching) before writing yours. + +```javascript +'use strict' + +const { channel, addHook } = require('./helpers/instrument') +const shimmer = require('../../../datadog-shimmer') + +// Channel naming convention: apm::: +// Events: start, finish, error, async-start, async-finish +const startCh = channel('apm:::start') +const finishCh = channel('apm:::finish') +const errorCh = channel('apm:::error') + +addHook({ name: '', versions: ['>=1.0'] }, (moduleExports) => { + shimmer.wrap(moduleExports, 'methodToWrap', function (original) { + return function wrappedMethod (...args) { + if (!startCh.hasSubscribers) { + return original.apply(this, args) + } + + const ctx = { /* relevant context */ } + return startCh.runStores(ctx, () => { + try { + const result = original.apply(this, args) + finishCh.publish(ctx) + return result + } catch (err) { + ctx.error = err + errorCh.publish(ctx) + throw err + } + }) + } + }) + return moduleExports +}) +``` + +**Key patterns:** +- Always guard with `if (!startCh.hasSubscribers)` for performance — skip instrumentation if no plugin is listening +- Use `startCh.runStores(ctx, () => {...})` to propagate async context +- Use `shimmer.wrap()` to patch methods non-destructively +- The `versions` array is a semver range; check existing instrumentations for precedents +- For multiple files in a package: use `file: 'path/within/package.js'` in `addHook` +- For multiple module names mapping to the same hooks: call `addHook` multiple times + +## Step 2: Create the Plugin Directory and File + +```bash +mkdir -p packages/datadog-plugin-/{src,test} +``` + +### Choosing the Right Base Class + +| Scenario | Base Class | Import Path | +|---|---|---| +| Creating trace spans for a single operation type | `TracingPlugin` | `../../dd-trace/src/plugins/tracing` | +| Wrapping an outbound client call (HTTP, gRPC, DB) | `OutboundPlugin` extends `TracingPlugin` | `../../dd-trace/src/plugins/outbound` | +| Wrapping an inbound server/consumer call | `InboundPlugin` extends `TracingPlugin` | `../../dd-trace/src/plugins/inbound` | +| Key-value cache client (Redis, Memcached) | `CachePlugin` extends `TracingPlugin` | `../../dd-trace/src/plugins/cache` | +| Multiple sub-concerns (producer + consumer, or tracing + code-origin) | `CompositePlugin` | `../../dd-trace/src/plugins/composite` | +| Non-tracing feature only | `Plugin` | `../../dd-trace/src/plugins/plugin` | + +### Template: Simple TracingPlugin + +```javascript +'use strict' + +const TracingPlugin = require('../../dd-trace/src/plugins/tracing') + +class MyPlugin extends TracingPlugin { + static id = '' // must match module name + static operation = '' // e.g., 'query', 'send', 'request' + static system = '' // e.g., 'redis', 'kafka' (used for peer.service) + + bindStart (ctx) { + const { relevantField } = ctx + + this.startSpan({ + resource: relevantField, + service: this.serviceName(), + meta: { + 'some.tag': relevantField + } + }, ctx) + } + + bindFinish (ctx) { + this.finish() + } + + bindError (ctx) { + this.finish(ctx.error) + } +} + +module.exports = MyPlugin +``` + +### Template: CompositePlugin + +```javascript +'use strict' + +const CompositePlugin = require('../../dd-trace/src/plugins/composite') +const ProducerPlugin = require('./producer') +const ConsumerPlugin = require('./consumer') + +class MyPlugin extends CompositePlugin { + static id = '' + + static get plugins () { + return { + producer: ProducerPlugin, + consumer: ConsumerPlugin + } + } +} + +module.exports = MyPlugin +``` + +For composite plugins, create separate files in `src/` for each sub-plugin (e.g., `src/producer.js`, `src/consumer.js`). + +## Step 3: Register the Plugin + +Add an entry to `packages/dd-trace/src/plugins/index.js`: + +```javascript +// Inside the plugins object: +get '' () { return require('../../../datadog-plugin-/src') }, +``` + +If multiple npm package names map to the same plugin (e.g., `redis` and `@redis/client`), add one getter per name. + +## Step 4: Add TypeScript Definitions + +In `index.d.ts`, add to the `plugins` namespace: + +```typescript +// In the Plugins interface: +'': plugins.; + +// Add a plugin interface (in alphabetical order with other plugin interfaces): +interface extends Instrumentation {} +// Or with config options: +interface extends Instrumentation { + optionName?: string | boolean; +} +``` + +## Step 5: Update docs/test.ts + +Add a type-check call in `docs/test.ts`: + +```typescript +tracer.use(''); +// Or with options: +tracer.use('', { optionName: 'value' }); +``` + +## Step 6: Document in docs/API.md + +Add a section in `docs/API.md` (alphabetically ordered): + +```markdown +
+ +This plugin automatically patches the []() module. + +| Option | Default | Description | +|--------|---------|-------------| +| `service` | | Service name override. | +``` + +## Step 7: Add to CI Workflow + +Add a job to `.github/workflows/apm-integrations.yml`: + +```yaml +: + runs-on: ubuntu-latest + env: + PLUGINS: + # SERVICES: # if external services needed + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/testagent/start + - uses: ./.github/actions/node + with: + version: ${{ matrix.node-version }} + - uses: ./.github/actions/install + - run: yarn test:plugins:ci + strategy: + matrix: + node-version: [18, 22] +``` + +Check `.github/workflows/apm-integrations.yml` for the exact current step format used by other plugins. + +## Step 8: Write Tests + +### Unit Tests + +Create `packages/datadog-plugin-/test/index.spec.js`: + +```javascript +'use strict' + +const agent = require('../../dd-trace/test/plugins/agent') +const { withVersions } = require('../../dd-trace/test/setup/mocha') + +describe('Plugin', () => { + describe('', () => { + withVersions('', '', (version) => { + let myLib + + beforeEach(() => { + return agent.load('') + }) + + beforeEach(() => { + myLib = require(`../../../versions/@${version}`) + }) + + afterEach(() => { + return agent.close({ ritmReset: false }) + }) + + it('should create a span', (done) => { + agent.use(traces => { + const span = traces[0][0] + expect(span.name).to.equal('.') + expect(span.service).to.equal('test-') + }).then(done, done) + + // trigger the instrumented operation + }) + }) + }) +}) +``` + +**Key test helpers:** +- `withVersions(pluginName, moduleName, cb)` — runs tests across installed versions +- `agent.load(pluginName)` — starts a test agent and loads the plugin +- `agent.close({ ritmReset: false })` — tears down (use `ritmReset: false` to preserve require cache) +- `agent.use(traces => { ... })` — asserts on captured traces +- `withNamingSchema(agent, ...)` — tests naming schema conventions +- `withPeerService(agent, ...)` — tests peer service tag + +### ESM Integration Tests + +ESM tests verify the plugin works with native ES module imports. They live in `packages/datadog-plugin-/test/integration-test/` and use a `FakeAgent` to assert on captured spans. + +Create `packages/datadog-plugin-/test/integration-test/server.mjs` — a minimal ESM script that initialises the tracer and triggers the instrumented operation: + +```javascript +import 'dd-trace/init.js' +import myLib from '' + +// trigger the instrumented operation +await myLib.someOperation() +``` + +Create `packages/datadog-plugin-/test/integration-test/client.spec.js` — the test that spawns the ESM server and asserts spans arrive: + +```javascript +'use strict' + +const assert = require('node:assert/strict') + +const { + FakeAgent, + sandboxCwd, + useSandbox, + checkSpansForServiceName, + spawnPluginIntegrationTestProcAndExpectExit, + varySandbox, +} = require('../../../../integration-tests/helpers') +const { withVersions } = require('../../../dd-trace/test/setup/mocha') + +describe('esm', () => { + let agent + let proc + let variants + + withVersions('', '', version => { + useSandbox([`'@${version}'`], false, [ + './packages/datadog-plugin-/test/integration-test/*']) + + beforeEach(async () => { + agent = await new FakeAgent().start() + }) + + before(async function () { + variants = varySandbox('server.mjs', '', '') + }) + + afterEach(async () => { + proc && proc.kill() + await agent.stop() + }) + + for (const variant of varySandbox.VARIANTS) { + it(`is instrumented ${variant}`, async () => { + const res = agent.assertMessageReceived(({ headers, payload }) => { + assert.strictEqual(headers.host, `127.0.0.1:${agent.port}`) + assert.ok(Array.isArray(payload)) + assert.strictEqual(checkSpansForServiceName(payload, '.'), true) + }) + + proc = await spawnPluginIntegrationTestProcAndExpectExit(sandboxCwd(), variants[variant], agent.port) + + await res + }).timeout(20000) + } + }) +}) +``` + +**Key points for ESM tests:** +- `varySandbox('server.mjs', bindingName, namedExport)` generates three import-style variants (`default`, `star`, `destructure`) from `server.mjs` so the instrumentation is verified under all ESM import patterns. +- `varySandbox.VARIANTS` is `['default', 'star', 'destructure']`. +- Pass `byPassDefault: true` as the fifth argument to `varySandbox` when the module has no default export (named-only packages). +- `useSandbox` installs the package versions into a temp sandbox dir; the second argument controls whether it runs `yarn install` inside the sandbox. +- `spawnPluginIntegrationTestProcAndExpectExit` spawns `node