dd-trace 5.131.1 → 5.133.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/ci/diagnose.js +9 -14
  2. package/ci/init.js +1 -2
  3. package/ci/test-optimization-validation/ci-discovery.js +9 -4
  4. package/ci/test-optimization-validation/executable.js +1 -5
  5. package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
  6. package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
  7. package/ci/test-optimization-validation/generated-files.js +6 -4
  8. package/ci/test-optimization-validation/generated-test-contract.js +7 -3
  9. package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
  10. package/ci/test-optimization-validation/manifest-schema.js +6 -7
  11. package/ci/test-optimization-validation/report-writer.js +6 -2
  12. package/ci/test-optimization-validation/runner-command.js +2 -4
  13. package/ci/test-optimization-validation/runner-contract.js +1 -2
  14. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
  15. package/ci/vitest-no-worker-init-setup.mjs +10 -7
  16. package/index.d.ts +116 -17
  17. package/initialize.mjs +1 -3
  18. package/openfeature/rules-based.d.ts +11 -0
  19. package/openfeature/rules-based.js +7 -0
  20. package/package.json +13 -11
  21. package/packages/datadog-core/src/storage.js +16 -0
  22. package/packages/datadog-esbuild/index.js +18 -12
  23. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  24. package/packages/datadog-instrumentations/src/ai.js +3 -3
  25. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  26. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  27. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  28. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  29. package/packages/datadog-instrumentations/src/cucumber.js +48 -33
  30. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  31. package/packages/datadog-instrumentations/src/electron.js +6 -6
  32. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  33. package/packages/datadog-instrumentations/src/fs.js +2 -2
  34. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  35. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  36. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
  37. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  38. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  39. package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
  40. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  41. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
  42. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +64 -11
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  48. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  49. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  50. package/packages/datadog-instrumentations/src/jest.js +122 -11
  51. package/packages/datadog-instrumentations/src/knex.js +9 -5
  52. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  53. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  54. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  55. package/packages/datadog-instrumentations/src/mocha/utils.js +127 -38
  56. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
  57. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
  58. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  59. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  60. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  61. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  62. package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
  63. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  64. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  65. package/packages/datadog-instrumentations/src/openai.js +7 -0
  66. package/packages/datadog-instrumentations/src/pino.js +5 -6
  67. package/packages/datadog-instrumentations/src/playwright.js +3 -0
  68. package/packages/datadog-instrumentations/src/redis.js +27 -13
  69. package/packages/datadog-instrumentations/src/router.js +30 -0
  70. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  71. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
  72. package/packages/datadog-instrumentations/src/vitest-main.js +9 -17
  73. package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
  74. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  75. package/packages/datadog-instrumentations/src/vitest.js +2 -2
  76. package/packages/datadog-instrumentations/src/webdriverio.js +118 -21
  77. package/packages/datadog-instrumentations/src/ws.js +1 -4
  78. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  79. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  80. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  81. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  82. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  83. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  84. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  85. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  86. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  87. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  88. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  89. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +18 -5
  90. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  91. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  92. package/packages/datadog-plugin-cypress/src/support.js +1 -4
  93. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  94. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  95. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  96. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  97. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  99. package/packages/datadog-plugin-mocha/src/index.js +223 -39
  100. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +453 -0
  101. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  102. package/packages/datadog-plugin-next/src/index.js +1 -2
  103. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  104. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  105. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  106. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  107. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  108. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  109. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  110. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  111. package/packages/datadog-webpack/index.js +55 -43
  112. package/packages/datadog-webpack/src/loader.js +56 -8
  113. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  114. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  115. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  116. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  117. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  118. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  119. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  120. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  121. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  122. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  123. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  124. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  125. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  126. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  127. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  128. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  129. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  130. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  131. package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
  132. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  133. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  134. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  135. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  136. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  137. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  138. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  139. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  140. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  141. package/packages/dd-trace/src/config/defaults.js +5 -5
  142. package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
  143. package/packages/dd-trace/src/config/helper.js +34 -3
  144. package/packages/dd-trace/src/config/index.js +10 -9
  145. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  146. package/packages/dd-trace/src/config/parsers.js +2 -2
  147. package/packages/dd-trace/src/config/supported-configurations.json +35 -0
  148. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  149. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  150. package/packages/dd-trace/src/debugger/constants.js +1 -0
  151. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
  152. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
  153. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -2
  154. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  155. package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
  156. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  157. package/packages/dd-trace/src/debugger/index.js +24 -2
  158. package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
  159. package/packages/dd-trace/src/dogstatsd.js +1 -1
  160. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  161. package/packages/dd-trace/src/exporters/common/request.js +2 -5
  162. package/packages/dd-trace/src/exporters/common/url.js +1 -1
  163. package/packages/dd-trace/src/id.js +15 -5
  164. package/packages/dd-trace/src/lambda/handler.js +179 -19
  165. package/packages/dd-trace/src/lambda/index.js +15 -1
  166. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  167. package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
  168. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  169. package/packages/dd-trace/src/llmobs/constants/tags.js +13 -0
  170. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  171. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  172. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  173. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  174. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  175. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  176. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
  177. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  178. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  179. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  180. package/packages/dd-trace/src/llmobs/sdk.js +23 -6
  181. package/packages/dd-trace/src/llmobs/span_processor.js +18 -8
  182. package/packages/dd-trace/src/llmobs/tagger.js +56 -3
  183. package/packages/dd-trace/src/llmobs/util.js +1 -2
  184. package/packages/dd-trace/src/llmobs/writers/base.js +7 -6
  185. package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
  186. package/packages/dd-trace/src/log/log.js +1 -5
  187. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
  188. package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
  189. package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
  190. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  191. package/packages/dd-trace/src/openfeature/flagging_provider.js +59 -3
  192. package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
  193. package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
  194. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  195. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  196. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  197. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  198. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
  199. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
  200. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  201. package/packages/dd-trace/src/otel-sampling.js +4 -5
  202. package/packages/dd-trace/src/otel-thread-ctx.js +81 -15
  203. package/packages/dd-trace/src/plugins/database.js +7 -16
  204. package/packages/dd-trace/src/plugins/util/git.js +9 -25
  205. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  206. package/packages/dd-trace/src/plugins/util/test.js +48 -4
  207. package/packages/dd-trace/src/priority_sampler.js +1 -2
  208. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  209. package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
  210. package/packages/dd-trace/src/proxy.js +3 -2
  211. package/packages/dd-trace/src/ritm.js +4 -2
  212. package/packages/dd-trace/src/serverless.js +10 -0
  213. package/packages/dd-trace/src/span_processor.js +13 -17
  214. package/packages/dd-trace/src/storage-channels.js +112 -46
  215. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  216. package/packages/dd-trace/src/util.js +29 -0
  217. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  218. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  219. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  220. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  221. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  222. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  223. package/vendor/dist/protobufjs/index.js +1 -1
  224. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  225. package/vendor/dist/shell-quote/index.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.131.1",
3
+ "version": "5.133.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -109,7 +109,7 @@
109
109
  "test:integration:webdriverio": "mocha --timeout 60000 \"integration-tests/webdriverio/${SPEC:-*}.spec.js\"",
110
110
  "test:integration:webdriverio:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/webdriverio/${SPEC:-*}.spec.js\"",
111
111
  "test:integration:vitest": "mocha --timeout 60000 \"integration-tests/vitest/${SPEC:-vitest*}.spec.js\"",
112
- "test:integration:vitest:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/vitest/${SPEC:-vitest*}.spec.js\"",
112
+ "test:integration:vitest:coverage": "node ./integration-tests/vitest/run-with-coverage.js --timeout 60000 \"integration-tests/vitest/${SPEC:-vitest*}.spec.js\"",
113
113
  "test:integration:testopt": "mocha --timeout 120000 \"integration-tests/ci-visibility/*.spec.js\"",
114
114
  "test:integration:testopt:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 120000 \"integration-tests/ci-visibility/*.spec.js\"",
115
115
  "test:integration:profiler": "mocha --timeout 180000 \"integration-tests/profiler/*.spec.js\"",
@@ -143,7 +143,7 @@
143
143
  "bugs": {
144
144
  "url": "https://github.com/DataDog/dd-trace-js/issues"
145
145
  },
146
- "homepage": "https://github.com/DataDog/dd-trace-js#readme",
146
+ "homepage": "https://github.com/DataDog/dd-trace-js",
147
147
  "engines": {
148
148
  "node": ">=18"
149
149
  },
@@ -169,6 +169,8 @@
169
169
  "loader-hook.mjs",
170
170
  "openfeature.d.ts",
171
171
  "openfeature.js",
172
+ "openfeature/rules-based.js",
173
+ "openfeature/rules-based.d.ts",
172
174
  "packages/*/index.js",
173
175
  "packages/*/index.electron.js",
174
176
  "packages/*/lib/**/*",
@@ -193,7 +195,7 @@
193
195
  "@datadog/native-appsec": "11.0.3",
194
196
  "@datadog/native-iast-taint-tracking": "4.2.1",
195
197
  "@datadog/native-metrics": "4.0.0",
196
- "@datadog/pprof": "5.19.0",
198
+ "@datadog/pprof": "5.20.0",
197
199
  "@datadog/wasm-js-rewriter": "5.0.4",
198
200
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
199
201
  "@opentelemetry/api-logs": "<1.0.0",
@@ -202,11 +204,11 @@
202
204
  "devDependencies": {
203
205
  "@actions/core": "^3.0.1",
204
206
  "@actions/github": "^9.1.1",
205
- "@babel/helpers": "^8.0.5",
207
+ "@babel/helpers": "^8.0.7",
206
208
  "@eslint/eslintrc": "^3.3.7",
207
209
  "@eslint/js": "^10.0.1",
208
210
  "@eslint/plugin-kit": "^0.7.3",
209
- "@datadog/openfeature-node-server": "2.4.0",
211
+ "@datadog/openfeature-node-server": "2.5.0",
210
212
  "@msgpack/msgpack": "^3.1.3",
211
213
  "@openfeature/core": "^1.12.0",
212
214
  "@openfeature/server-sdk": "~1.23.0",
@@ -218,20 +220,20 @@
218
220
  "@yarnpkg/lockfile": "^1.1.0",
219
221
  "benchmark": "^2.1.4",
220
222
  "body-parser": "^2.3.0",
221
- "bun": "1.4.2",
223
+ "bun": "1.4.3",
222
224
  "c8": "^12.0.0",
223
225
  "codeowners-audit": "^2.9.0",
224
- "eslint": "^10.11.0",
226
+ "eslint": "^10.13.0",
225
227
  "eslint-plugin-cypress": "^7.0.2",
226
228
  "eslint-plugin-import-x": "^4.16.2",
227
- "eslint-plugin-jsdoc": "^64.5.4",
229
+ "eslint-plugin-jsdoc": "^65.3.12",
228
230
  "eslint-plugin-mocha": "^12.0.2",
229
- "eslint-plugin-n": "^18.4.0",
231
+ "eslint-plugin-n": "^18.5.0",
230
232
  "eslint-plugin-promise": "^7.3.0",
231
233
  "eslint-plugin-regexp": "^3.3.1",
232
234
  "eslint-plugin-sonarjs": "^4.2.2",
233
235
  "eslint-plugin-unicorn": "^74.0.0",
234
- "express": "^5.1.0",
236
+ "express": "^5.3.0",
235
237
  "glob": "^10.4.5",
236
238
  "globals": "^17.13.0",
237
239
  "graphql": "*",
@@ -56,12 +56,28 @@ const isACFActive = (() => {
56
56
  if (!isACFActive) {
57
57
  const superGetStore = AsyncLocalStorage.prototype.getStore
58
58
  const superEnterWith = AsyncLocalStorage.prototype.enterWith
59
+ // @ts-expect-error Node calls this undocumented AsyncLocalStorage hook.
60
+ const superPropagate = AsyncLocalStorage.prototype._propagate
59
61
 
60
62
  // Without ACF, ALS writes the entered value onto the async resource. Keep the
61
63
  // real store off the resource by entering a small handle and mapping it to
62
64
  // the store through a WeakMap, then reversing the lookup on read.
63
65
  const stores = new WeakMap()
64
66
 
67
+ /**
68
+ * Node can omit the trigger resource while the inspector is paused. Guard
69
+ * the shared hook because every enabled AsyncLocalStorage receives the call.
70
+ *
71
+ * @param {object} resource
72
+ * @param {object | undefined} triggerResource
73
+ * @param {string} type
74
+ */
75
+ AsyncLocalStorage.prototype._propagate = function _propagate (resource, triggerResource, type) {
76
+ if (triggerResource) {
77
+ superPropagate.call(this, resource, triggerResource, type)
78
+ }
79
+ }
80
+
65
81
  /**
66
82
  * @param {Store<unknown>} [store]
67
83
  */
@@ -8,6 +8,7 @@ const { pathToFileURL, fileURLToPath } = require('node:url')
8
8
  const instrumentations = require('../datadog-instrumentations/src/helpers/instrumentations')
9
9
  const extractPackageAndModulePath = require('../datadog-instrumentations/src/helpers/extract-package-and-module-path')
10
10
  const hooks = require('../datadog-instrumentations/src/helpers/hooks')
11
+ const { getValueFromEnvSources } = require('../dd-trace/src/config/helper')
11
12
  const log = require('./src/log')
12
13
  const { createEsmResolver } = require('./src/resolver')
13
14
  const { isESMFile, processModule } = require('./src/utils')
@@ -60,6 +61,7 @@ for (const builtin of builtinModules) {
60
61
 
61
62
  // eslint-disable-next-line eslint-rules/eslint-process-env
62
63
  const DD_IAST_ENABLED = process.env.DD_IAST_ENABLED?.toLowerCase() === 'true' || process.env.DD_IAST_ENABLED === '1'
64
+ const DD_BUNDLER_INJECT_GIT_METADATA = getValueFromEnvSources('DD_BUNDLER_INJECT_GIT_METADATA') !== false
63
65
 
64
66
  module.exports.name = 'datadog-esbuild'
65
67
 
@@ -143,26 +145,30 @@ globalThis.__dirname ??= $dd_dirname(globalThis.__filename);
143
145
  ${build.initialOptions.banner.js}`
144
146
  }
145
147
 
146
- // Get git metadata at build time and add it to the banner for both ESM and CommonJS builds
147
- const gitMetadata = getGitMetadata()
148
- if (gitMetadata.repositoryURL || gitMetadata.commitSHA) {
149
- build.initialOptions.banner ??= {}
150
- build.initialOptions.banner.js ??= ''
148
+ // Bake DD_GIT_* into the banner unless DD_BUNDLER_INJECT_GIT_METADATA=false (stable artifact hashes).
149
+ if (DD_BUNDLER_INJECT_GIT_METADATA) {
150
+ const gitMetadata = getGitMetadata()
151
+ if (gitMetadata.repositoryURL || gitMetadata.commitSHA) {
152
+ build.initialOptions.banner ??= {}
153
+ build.initialOptions.banner.js ??= ''
151
154
 
152
- build.initialOptions.banner.js = `if (typeof process === 'object' && process !== null &&
155
+ build.initialOptions.banner.js = `if (typeof process === 'object' && process !== null &&
153
156
  process.env !== null && typeof process.env === 'object') {
154
157
  ${gitMetadata.repositoryURL ? `process.env.DD_GIT_REPOSITORY_URL = '${gitMetadata.repositoryURL}';` : ''}
155
158
  ${gitMetadata.commitSHA ? `process.env.DD_GIT_COMMIT_SHA = '${gitMetadata.commitSHA}';` : ''}
156
159
  }
157
160
  ${build.initialOptions.banner.js}`
158
161
 
159
- log.debug(
160
- 'Automatically injected git metadata (DD_GIT_REPOSITORY_URL: %s, DD_GIT_COMMIT_SHA: %s)',
161
- gitMetadata.repositoryURL || 'not available',
162
- gitMetadata.commitSHA || 'not available'
163
- )
162
+ log.debug(
163
+ 'Automatically injected git metadata (DD_GIT_REPOSITORY_URL: %s, DD_GIT_COMMIT_SHA: %s)',
164
+ gitMetadata.repositoryURL || 'not available',
165
+ gitMetadata.commitSHA || 'not available'
166
+ )
167
+ } else {
168
+ log.warn('No git metadata available - skipping injection')
169
+ }
164
170
  } else {
165
- log.warn('No git metadata available - skipping injection')
171
+ log.debug('Skipping git metadata injection (DD_BUNDLER_INJECT_GIT_METADATA=false)')
166
172
  }
167
173
 
168
174
  let resolver
@@ -42,9 +42,9 @@ function wrapExecuteWithCallback (executeWithCallback) {
42
42
  function wrapProcess (process) {
43
43
  return function (...args) {
44
44
  const cb = args[0]
45
- if (!ch.start.hasSubscribers || typeof cb !== 'function') return process.apply(this, args)
46
-
47
- if (this[kTracingCallbackCommand]) return process.apply(this, args)
45
+ if (!ch.start.hasSubscribers || typeof cb !== 'function' || this[kTracingCallbackCommand]) {
46
+ return process.apply(this, args)
47
+ }
48
48
 
49
49
  const ctx = getContext(this)
50
50
 
@@ -228,11 +228,11 @@ for (const hook of getHooks('ai').values()) {
228
228
 
229
229
  // The SDK builds a model from a string id, in which case only the resolved instance is
230
230
  // worth wrapping; when the caller passed an instance, that is the one the SDK calls.
231
- if (typeof model !== 'string' && model !== ctx.result) {
231
+ if (typeof model === 'string' || model === ctx.result) {
232
+ wrapModel(ctx.result)
233
+ } else {
232
234
  wrapModel(model)
233
235
  wrappedModels.add(ctx.result)
234
- } else {
235
- wrapModel(ctx.result)
236
236
  }
237
237
  },
238
238
  })
@@ -2,7 +2,6 @@
2
2
 
3
3
  const { channel, tracingChannel } = require('dc-polyfill')
4
4
  const shimmer = require('../../datadog-shimmer')
5
- const { addHook, getHooks } = require('./helpers/instrument')
6
5
  const LAZY_DURABLE_PROMISE_METHODS = require('./aws-durable-execution-sdk-js-context-methods')
7
6
 
8
7
  for (const method of LAZY_DURABLE_PROMISE_METHODS) {
@@ -102,8 +101,3 @@ function observeDurablePromise (dp, onSettle) {
102
101
  instrumentDurablePromiseProto(Object.getPrototypeOf(dp))
103
102
  dp[ON_SETTLE] = onSettle
104
103
  }
105
-
106
- for (const hook of getHooks('@aws/durable-execution-sdk-js').values()) {
107
- hook.file = null
108
- addHook(hook, exports => exports)
109
- }
@@ -2,12 +2,14 @@
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
4
  const { channel, addHook } = require('./helpers/instrument')
5
+ const sendSonic = require('./nova-sonic')
5
6
 
6
7
  const patchedClientConfigProtocols = new WeakSet()
7
8
  const patchedCommandPrototypes = new WeakSet()
9
+ const patchedSmithyClients = new WeakSet()
8
10
 
9
11
  // Resource identifiers that already match the channel-suffix slug. Anything
10
- // else falls back to `'default'`. Hoisted out of the per-call hot path so we
12
+ // else falls back to `'aws'`. Hoisted out of the per-call hot path so we
11
13
  // don't allocate a fresh Array literal + run `.includes` on every AWS send.
12
14
  const KNOWN_CHANNEL_SUFFIXES = new Set([
13
15
  'cloudwatchlogs',
@@ -161,6 +163,9 @@ function wrapSmithySend (send) {
161
163
  const channels = getChannelBag(channelSuffix)
162
164
  const clientName = getClientName(this.constructor)
163
165
  const operation = getOperationName(command.constructor)
166
+ if (channelSuffix === 'bedrockruntime' && operation === 'invokeModelWithBidirectionalStream') {
167
+ return sendSonic(send, this, command, args)
168
+ }
164
169
  const request = {
165
170
  operation,
166
171
  params: command.input,
@@ -320,18 +325,24 @@ function getChannelSuffix (name) {
320
325
  // some resource identifiers have spaces between ex: bedrock runtime
321
326
  name = String(name).replaceAll(' ', '')
322
327
  if (KNOWN_CHANNEL_SUFFIXES.has(name)) return name
323
- return CHANNEL_SUFFIX_ALIASES.get(name) ?? 'default'
328
+ return CHANNEL_SUFFIX_ALIASES.get(name) ?? 'aws'
324
329
  }
325
330
 
326
- addHook({ name: '@smithy/smithy-client', versions: ['>=1.0.3'] }, smithy => {
327
- shimmer.wrap(smithy.Client.prototype, 'send', wrapSmithySend)
331
+ /** @param {{ Client: Function }} smithy */
332
+ function patchSmithyClient (smithy) {
333
+ const prototype = smithy.Client.prototype
334
+ // Recent smithy-client releases re-export core/client. Loading both entry points must not
335
+ // install two observers on the same send (and consume/charge every duplex event twice).
336
+ if (!patchedSmithyClients.has(prototype)) {
337
+ shimmer.wrap(prototype, 'send', wrapSmithySend)
338
+ patchedSmithyClients.add(prototype)
339
+ }
328
340
  return smithy
329
- })
341
+ }
330
342
 
331
- addHook({ name: '@aws-sdk/smithy-client', versions: ['>=3'] }, smithy => {
332
- shimmer.wrap(smithy.Client.prototype, 'send', wrapSmithySend)
333
- return smithy
334
- })
343
+ addHook({ name: '@smithy/smithy-client', versions: ['>=1.0.3'] }, patchSmithyClient)
344
+
345
+ addHook({ name: '@aws-sdk/smithy-client', versions: ['>=3'] }, patchSmithyClient)
335
346
 
336
347
  // `@aws-sdk/client-*` >= 3.1046.0 dropped `@smithy/smithy-client` and now
337
348
  // extends from `@smithy/core/client` directly. The `Client.send` contract is
@@ -341,10 +352,7 @@ addHook({
341
352
  name: '@smithy/core',
342
353
  file: 'dist-cjs/submodules/client/index.js',
343
354
  versions: ['>=3.24.0'],
344
- }, smithyCoreClient => {
345
- shimmer.wrap(smithyCoreClient.Client.prototype, 'send', wrapSmithySend)
346
- return smithyCoreClient
347
- })
355
+ }, patchSmithyClient)
348
356
 
349
357
  addHook({ name: 'aws-sdk', versions: ['>=2.3.0'] }, AWS => {
350
358
  shimmer.wrap(AWS.config, 'setPromisesDependency', setPromisesDependency => {
@@ -11,6 +11,7 @@ const finishCh = channel('apm:cassandra-driver:query:finish')
11
11
  const errorCh = channel('apm:cassandra-driver:query:error')
12
12
  const connectCh = channel('apm:cassandra-driver:query:connect')
13
13
 
14
+ /** @type {Record<string, unknown> & { error?: Error }} */
14
15
  let startCtx = {}
15
16
 
16
17
  addHook({ name: 'cassandra-driver', versions: ['>=3.0.0'] }, cassandra => {
@@ -24,23 +25,23 @@ addHook({ name: 'cassandra-driver', versions: ['>=3.0.0'] }, cassandra => {
24
25
  startCtx = { keyspace: this.keyspace, query: queries, contactPoints: this.options && this.options.contactPoints }
25
26
  return startCh.runStores(startCtx, () => {
26
27
  if (typeof cb === 'function') {
27
- arguments[lastIndex] = wrapCallback(finishCh, errorCh, startCtx, cb)
28
+ arguments[lastIndex] = wrapCallback(startCtx, cb)
28
29
  }
29
30
 
30
31
  try {
31
32
  const res = batch.apply(this, arguments)
32
33
  if (typeof res === 'function') {
33
- return wrapCallback(finishCh, errorCh, startCtx, res)
34
+ return wrapCallback(startCtx, res)
34
35
  }
35
36
  if (!res) {
36
37
  return res
37
38
  }
38
39
  return res.then(
39
- () => finish(finishCh, errorCh, startCtx),
40
- err => finish(finishCh, errorCh, startCtx, err)
40
+ () => finish(startCtx),
41
+ err => finish(startCtx, err)
41
42
  )
42
43
  } catch (e) {
43
- finish(finishCh, errorCh, startCtx, e)
44
+ finish(startCtx, e)
44
45
  throw e
45
46
  }
46
47
  })
@@ -58,8 +59,8 @@ addHook({ name: 'cassandra-driver', versions: ['>=4.4'], patchDefault: false },
58
59
  const promise = _execute.apply(this, arguments)
59
60
 
60
61
  promise.then(
61
- () => finish(finishCh, errorCh, startCtx),
62
- err => finish(finishCh, errorCh, startCtx, err)
62
+ () => finish(startCtx),
63
+ err => finish(startCtx, err)
63
64
  )
64
65
  return promise
65
66
  })
@@ -74,10 +75,7 @@ const isValid = (args) => {
74
75
  addHook({ name: 'cassandra-driver', versions: ['3 - 4.3'], patchDefault: false }, (cassandra) => {
75
76
  shimmer.wrap(cassandra.Client.prototype, '_innerExecute', _innerExecute =>
76
77
  function (query, params, execOptions, callback) {
77
- if (!startCh.hasSubscribers) {
78
- return _innerExecute.apply(this, arguments)
79
- }
80
- if (!isValid(arguments)) {
78
+ if (!startCh.hasSubscribers || !isValid(arguments)) {
81
79
  return _innerExecute.apply(this, arguments)
82
80
  }
83
81
 
@@ -87,13 +85,13 @@ addHook({ name: 'cassandra-driver', versions: ['3 - 4.3'], patchDefault: false }
87
85
  const cb = arguments[lastIndex]
88
86
 
89
87
  if (typeof cb === 'function') {
90
- arguments[lastIndex] = wrapCallback(finishCh, errorCh, startCtx, cb)
88
+ arguments[lastIndex] = wrapCallback(startCtx, cb)
91
89
  }
92
90
 
93
91
  try {
94
92
  return _innerExecute.apply(this, arguments)
95
93
  } catch (e) {
96
- finish(finishCh, errorCh, startCtx, e)
94
+ finish(startCtx, e)
97
95
  throw e
98
96
  }
99
97
  })
@@ -156,7 +154,11 @@ addHook({ name: 'cassandra-driver', versions: ['3 - 3.2'], file: 'lib/request-ha
156
154
  return RequestHandler
157
155
  })
158
156
 
159
- function finish (finishCh, errorCh, ctx, error) {
157
+ /**
158
+ * @param {Record<string, unknown> & { error?: Error }} ctx
159
+ * @param {Error} [error]
160
+ */
161
+ function finish (ctx, error) {
160
162
  if (error) {
161
163
  ctx.error = error
162
164
  errorCh.publish(ctx)
@@ -164,19 +166,22 @@ function finish (finishCh, errorCh, ctx, error) {
164
166
  finishCh.runStores(ctx, () => {})
165
167
  }
166
168
 
167
- function wrapCallback (finishCh, errorCh, ctx, callback) {
169
+ /**
170
+ * @param {Record<string, unknown> & { error?: Error }} ctx
171
+ * @param {Parameters<typeof finishCh.runStores>[1]} callback
172
+ */
173
+ function wrapCallback (ctx, callback) {
168
174
  return shimmer.wrapCallback(callback, callback => function (err) {
169
175
  if (err) {
170
176
  ctx.error = err
171
177
  errorCh.publish(ctx)
172
178
  }
173
- return finishCh.runStores(ctx, callback, this, ...arguments)
179
+ return finishCh.runStores(
180
+ ctx, /** @type {Parameters<typeof finishCh.runStores>[1]} */ (callback), this, ...arguments
181
+ )
174
182
  })
175
183
  }
176
184
 
177
185
  function isRequestValid (exec, args, length) {
178
- if (!exec) return false
179
- if (args.length !== length || typeof args[length - 1] !== 'function') return false
180
-
181
- return true
186
+ return !!exec && args.length === length && typeof args[length - 1] === 'function'
182
187
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  const { tracingChannel } = require('dc-polyfill')
4
4
  const shimmer = require('../../datadog-shimmer')
5
- const { addHook, getHooks } = require('./helpers/instrument')
6
5
  const queryChannel = tracingChannel('orchestrion:@anthropic-ai/claude-agent-sdk:query')
7
6
 
8
7
  const stepCh = tracingChannel('apm:claude-agent-sdk:step')
@@ -638,28 +637,15 @@ function wrapQueryAsyncIterator (asyncIterator, ctx) {
638
637
  }
639
638
  }
640
639
 
641
- let querySubscribed = false
640
+ queryChannel.subscribe({
641
+ start: onQueryStart,
642
+ end (ctx) {
643
+ if (!ctx.claudeAgentSdkTracing) return
642
644
 
643
- for (const hook of getHooks('@anthropic-ai/claude-agent-sdk').values()) {
644
- hook.file = null
645
+ const { result } = ctx
645
646
 
646
- addHook(hook, exports => {
647
- if (!querySubscribed) {
648
- querySubscribed = true
649
- queryChannel.subscribe({
650
- start: onQueryStart,
651
- end (ctx) {
652
- if (!ctx.claudeAgentSdkTracing) return
647
+ ctx.streamResolved = false
653
648
 
654
- const { result } = ctx
655
-
656
- ctx.streamResolved = false
657
-
658
- shimmer.wrap(result, Symbol.asyncIterator, asyncIterator => wrapQueryAsyncIterator(asyncIterator, ctx))
659
- },
660
- })
661
- }
662
-
663
- return exports
664
- })
665
- }
649
+ shimmer.wrap(result, Symbol.asyncIterator, asyncIterator => wrapQueryAsyncIterator(asyncIterator, ctx))
650
+ },
651
+ })
@@ -102,7 +102,7 @@ const efdRetryCountByPickleId = new Map()
102
102
  const efdSlowAbortedPickleIds = new Set()
103
103
  const finishedParallelSuites = new Set()
104
104
  const numAttemptToCtx = new Map()
105
- const newTestsByTestFullname = new Map()
105
+ const efdStatusesByPickleId = new Map()
106
106
  const attemptToFixTestsByTestFullname = new Map()
107
107
  const modifiedTestsByPickleId = new Map()
108
108
  const runnerToRetryState = new WeakMap()
@@ -200,13 +200,13 @@ function getSuiteStatusFromTestStatuses (testStatuses) {
200
200
  return 'pass'
201
201
  }
202
202
 
203
- function publishWorkerEfdRetryCount (pickle, retryCount) {
203
+ function publishWorkerEfdRetryCount (pickle, retryCount, isModified) {
204
204
  const message = {
205
205
  [DD_EFD_RETRY_COUNT_MESSAGE]: {
206
206
  pickleId: pickle.id,
207
207
  retryCount,
208
+ isModified,
208
209
  testFileAbsolutePath: pickle.uri,
209
- testName: pickle.name,
210
210
  },
211
211
  }
212
212
 
@@ -220,10 +220,12 @@ function publishWorkerEfdRetryCount (pickle, retryCount) {
220
220
  }
221
221
 
222
222
  try {
223
- const { isMainThread, parentPort } = require('node:worker_threads')
223
+ const { isMainThread, parentPort, workerData } = require('node:worker_threads')
224
224
  if (isMainThread || !parentPort) return
225
225
 
226
- parentPort.postMessage(message)
226
+ // Share Cucumber's envelope port so EFD metadata arrives before test results.
227
+ const port = workerData?.port ?? parentPort
228
+ port.postMessage(message)
227
229
  } catch {
228
230
  // ignore IPC errors
229
231
  }
@@ -385,14 +387,16 @@ function handleParallelTestCaseFinished (pickle, worstTestStepResult, usesNumeri
385
387
  const testFileAbsolutePath = pickle.uri
386
388
  const finished = (pickleResultByFile[testFileAbsolutePath] ||= [])
387
389
 
388
- if (shouldRunEarlyFlakeDetection() && isNew) {
389
- const testFullname = `${pickle.uri}:${pickle.name}`
390
- let testStatuses = newTestsByTestFullname.get(testFullname)
390
+ const isAttemptToFix = isTestManagementTestsEnabled &&
391
+ getTestProperties(getTestSuitePath(testFileAbsolutePath, process.cwd()), pickle.name).attemptToFix
392
+
393
+ if (!isAttemptToFix && shouldRunEarlyFlakeDetection() && (isNew || modifiedTestsByPickleId.get(pickle.id))) {
394
+ let testStatuses = efdStatusesByPickleId.get(pickle.id)
391
395
  if (testStatuses) {
392
396
  testStatuses.push(status)
393
397
  } else {
394
398
  testStatuses = [status]
395
- newTestsByTestFullname.set(testFullname, testStatuses)
399
+ efdStatusesByPickleId.set(pickle.id, testStatuses)
396
400
  }
397
401
  let efdRetryCount = efdRetryCountByPickleId.get(pickle.id)
398
402
  if (efdRetryCount === undefined) {
@@ -404,11 +408,8 @@ function handleParallelTestCaseFinished (pickle, worstTestStepResult, usesNumeri
404
408
  efdSlowAbortedPickleIds.add(pickle.id)
405
409
  }
406
410
  }
407
- maybeRecordFinalParallelEfdStatus({ pickleId: pickle.id, testFileAbsolutePath, testFullname })
408
- } else if (
409
- isTestManagementTestsEnabled &&
410
- getTestProperties(getTestSuitePath(testFileAbsolutePath, process.cwd()), pickle.name).attemptToFix
411
- ) {
411
+ maybeRecordFinalParallelEfdStatus({ pickleId: pickle.id, testFileAbsolutePath })
412
+ } else if (isAttemptToFix) {
412
413
  const testFullname = `${pickle.uri}:${pickle.name}`
413
414
  let testStatuses = attemptToFixTestsByTestFullname.get(testFullname)
414
415
  if (testStatuses) {
@@ -495,8 +496,14 @@ function finishParallelSuiteIfDone (testFileAbsolutePath) {
495
496
  const finished = pickleResultByFile[testFileAbsolutePath]
496
497
  const expectedPickles = pickleByFile[testFileAbsolutePath]
497
498
 
498
- if (!finished || !expectedPickles || finished.length !== expectedPickles.length) return
499
- if (finishedParallelSuites.has(testFileAbsolutePath)) return
499
+ if (
500
+ !finished ||
501
+ !expectedPickles ||
502
+ finished.length !== expectedPickles.length ||
503
+ finishedParallelSuites.has(testFileAbsolutePath)
504
+ ) {
505
+ return
506
+ }
500
507
 
501
508
  finishedParallelSuites.add(testFileAbsolutePath)
502
509
  testSuiteFinishCh.publish({
@@ -505,29 +512,30 @@ function finishParallelSuiteIfDone (testFileAbsolutePath) {
505
512
  })
506
513
  }
507
514
 
508
- function maybeRecordFinalParallelEfdStatus ({ pickleId, testFileAbsolutePath, testFullname }) {
515
+ function maybeRecordFinalParallelEfdStatus ({ pickleId, testFileAbsolutePath }) {
509
516
  const efdRetryCount = efdRetryCountByPickleId.get(pickleId)
510
- const testStatuses = newTestsByTestFullname.get(testFullname)
517
+ const testStatuses = efdStatusesByPickleId.get(pickleId)
511
518
  const finished = pickleResultByFile[testFileAbsolutePath]
512
519
 
513
- if (efdRetryCount === undefined || !testStatuses || !finished) return
514
- if (testStatuses.length !== efdRetryCount + 1) return
520
+ if (efdRetryCount === undefined || !testStatuses || !finished || testStatuses.length !== efdRetryCount + 1) return
515
521
 
516
- finished.push(getTestStatusFromRetries(testStatuses))
517
- newTestsByTestFullname.delete(testFullname)
522
+ finished.push(efdRetryCount === 0 ? testStatuses[0] : getTestStatusFromRetries(testStatuses))
523
+ efdStatusesByPickleId.delete(pickleId)
518
524
  finishParallelSuiteIfDone(testFileAbsolutePath)
519
525
  }
520
526
 
521
527
  function handleEfdRetryCountMessage (message) {
522
- const { pickleId, retryCount, testFileAbsolutePath, testName } = message
528
+ const { pickleId, retryCount, isModified, testFileAbsolutePath } = message
523
529
 
524
- if (!pickleId || typeof retryCount !== 'number' || !testFileAbsolutePath || !testName) return
530
+ if (!pickleId || !testFileAbsolutePath) return
531
+
532
+ if (isModified) modifiedTestsByPickleId.set(pickleId, true)
533
+ if (typeof retryCount !== 'number') return
525
534
 
526
535
  efdRetryCountByPickleId.set(pickleId, retryCount)
527
536
  maybeRecordFinalParallelEfdStatus({
528
537
  pickleId,
529
538
  testFileAbsolutePath,
530
- testFullname: `${testFileAbsolutePath}:${testName}`,
531
539
  })
532
540
  }
533
541
 
@@ -912,12 +920,13 @@ function wrapRun (pl, isLatestVersion, version) {
912
920
  isNew = newTestPickleIds.has(this.pickle.id)
913
921
  }
914
922
 
915
- if (isNew || isModified) {
923
+ if (!isAttemptToFix && (isNew || isModified)) {
916
924
  isEfdRetry = numRetries > 0
917
925
  }
918
926
 
919
927
  if (
920
928
  shouldRunEarlyFlakeDetection() &&
929
+ !isAttemptToFix &&
921
930
  status !== 'skip' &&
922
931
  (isNew || isModified) &&
923
932
  !isEfdRetry &&
@@ -1265,7 +1274,8 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
1265
1274
  efdRetryCountByPickleId.clear()
1266
1275
  efdSlowAbortedPickleIds.clear()
1267
1276
  finishedParallelSuites.clear()
1268
- newTestsByTestFullname.clear()
1277
+ efdStatusesByPickleId.clear()
1278
+ modifiedTestsByPickleId.clear()
1269
1279
  sessionStartCh.publish({ command, frameworkVersion })
1270
1280
 
1271
1281
  if (!errorSkippableRequest && skippedSuites.length) {
@@ -1478,10 +1488,15 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1478
1488
  }
1479
1489
  }
1480
1490
  const originalRetry = this.options.retry
1481
- const isManagedRetry = isAttemptToFix || (shouldRunEarlyFlakeDetection() && (isNew || isModified))
1491
+ const isEfdTest = !isAttemptToFix && shouldRunEarlyFlakeDetection() && (isNew || isModified)
1492
+ const isManagedRetry = isAttemptToFix || isEfdTest
1482
1493
  if (isManagedRetry) {
1483
1494
  this.options.retry = 0
1484
1495
  }
1496
+ if (isWorker && isModified && isEfdTest) {
1497
+ // The coordinator must recognize modified EFD tests before their first result arrives.
1498
+ publishWorkerEfdRetryCount(pickle, undefined, true)
1499
+ }
1485
1500
  // TODO: for >=11 we could use `runTestCaseResult` instead of accumulating results in `lastStatusByPickleId`
1486
1501
  const firstExecutionStart = performance.now()
1487
1502
  let runTestCaseResult = await runTestCaseFunction.apply(this, arguments)
@@ -1493,7 +1508,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1493
1508
  const testStatuses = lastStatusByPickleId.get(pickle.id)
1494
1509
  const lastTestStatus = testStatuses.at(-1)
1495
1510
 
1496
- // New tests should not be marked as attempt to fix, so EFD + Attempt to fix should not be enabled at the same time
1511
+ // Attempt to Fix owns retries for managed tests, including modified tests.
1497
1512
  if (isAttemptToFix && lastTestStatus !== 'skip') {
1498
1513
  for (let retryIndex = 0; retryIndex < testManagementAttemptToFixRetries; retryIndex++) {
1499
1514
  numRetriesByPickleId.set(pickle.id, retryIndex + 1)
@@ -1502,8 +1517,8 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1502
1517
  }
1503
1518
  }
1504
1519
 
1505
- // If it's a new test and it hasn't been skipped, we run it again
1506
- if (shouldRunEarlyFlakeDetection() && lastTestStatus !== 'skip' && (isNew || isModified)) {
1520
+ // Retry eligible new or modified tests unless their first attempt was skipped.
1521
+ if (isEfdTest && lastTestStatus !== 'skip') {
1507
1522
  let efdRetryCount = efdRetryCountByPickleId.get(pickle.id)
1508
1523
  if (efdRetryCount === undefined) {
1509
1524
  efdRetryCount = getEfdRetryCountForDuration(firstExecutionDurationMs, earlyFlakeDetectionRetryPolicy)
@@ -1525,7 +1540,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1525
1540
  let shouldBePassedByEFD = false
1526
1541
  let shouldBePassedByTestManagement = false
1527
1542
  let shouldBeFailedByAttemptToFix = false
1528
- if ((isNew || isModified) && shouldRunEarlyFlakeDetection()) {
1543
+ if (isEfdTest) {
1529
1544
  /**
1530
1545
  * If Early Flake Detection (EFD) is enabled the logic is as follows:
1531
1546
  * - If all attempts for a test are failing, the test has failed and we will let the test process fail.
@@ -1587,7 +1602,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1587
1602
  return shouldBePassedByTestManagement
1588
1603
  }
1589
1604
 
1590
- if (isNewerCucumberVersion && shouldRunEarlyFlakeDetection() && (isNew || isModified)) {
1605
+ if (isNewerCucumberVersion && isEfdTest) {
1591
1606
  return shouldBePassedByEFD
1592
1607
  }
1593
1608