Skip to content

skill(add-apm-integrations): add rules from recent reviews + refactor into sub references#11760

Open
jordan-wong wants to merge 14 commits into
masterfrom
skill/r13-r33-from-toolkit
Open

skill(add-apm-integrations): add rules from recent reviews + refactor into sub references#11760
jordan-wong wants to merge 14 commits into
masterfrom
skill/r13-r33-from-toolkit

Conversation

@jordan-wong

@jordan-wong jordan-wong commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds ~20 rules to the canonical add-apm-integrations skill and refactors it from a single 794-line SKILL.md into a 215-line routing overview + 6 topic-oriented reference files under references/. Every rule either encodes a pattern already in dd-java-agent/instrumentation/ or is enforced by CI.

Structure after refactor

.claude/skills/add-apm-integrations/
├── SKILL.md                                  215 lines — Steps 1-12 routing overview
└── references/
    ├── advice-class.md                       167 lines — Step 7 (highest-risk step)
    ├── context-tracking.md                   102 lines — Step 4.1 (new: span-creating vs context-tracking)
    ├── instrumenter-module.md                104 lines — Step 5
    ├── muzzle.md                             125 lines — Step 9.2
    ├── naming-conventions.md                  27 lines — Step 4 + 4.2
    └── tests.md                              142 lines — Step 9.1

SKILL.md's Steps 1-12 headings and the bodies of Steps 1-3, 6, 8, and 10-12 are unchanged from master. Content growth is in the reference files.

Where the rules came from

Suggested review order

  1. SKILL.md — validate the routing overview.
  2. references/context-tracking.md — the only entirely new territory. Formalizes the pattern from dd-java-agent/instrumentation/rxjava/rxjava-2.0/ (extends InstrumenterModule.ContextTracking).
  3. references/advice-class.md + instrumenter-module.md — largest bodies of rules; highest reviewer value.
  4. references/muzzle.md, tests.md, naming-conventions.md — mostly formalize existing conventions; skim.

Skip the refactor commits (structural moves, no content changes). Diff against master for the final content.

Rule provenance

Rule Source
context-tracking.md — write InstrumenterModule.ContextTracking for reactive/async libs In-repo pattern: rxjava-2.0
context-tracking.md — Flowable overload rule (hook framework-internal overload) @amarziali on #11527
context-tracking.md — parent-child bridging test pattern In-repo pattern: rxjava-2.0/src/test/
naming-conventions.md — dir name must end with version or -common/-stubs/-iast CI: buildSrc/.../InstrumentationNamingPlugin.kt
naming-conventions.md — filename ↔ class name must match Java language rule (javac enforces at compile)
instrumenter-module.md — interface-only API JARs need ForTypeHierarchy + implementsInterface In-repo pattern: jms/javax-jms-1.1
instrumenter-module.md — no static constants for one-shot methods In-repo convention
instrumenter-module.md — no single-type helper for CallDepthThreadLocalMap @amarziali on #11527
instrumenter-module.mdinstrumentationNames() version-alias In-repo convention
instrumenter-module.md — preserve master's integration name when regenerating @PerfectSlayer on #11717
advice-class.md — single delegate method, not all overloads @amarziali on #11527
advice-class.mdonThrowable semantics (exit-on-target-throw) docs/how_instrumentations_work.md:532-552
advice-class.md — explicit charset when converting byte[]String SpotBugs DM_DEFAULT_ENCODING
advice-class.md — no NullPointerException catches SpotBugs DCN_NULLPOINTER_EXCEPTION
advice-class.md@AppliesOn for multiple advices docs/how_instrumentations_work.md
tests.md — Java tests only, no new .groovy files CI: Enforce Groovy Migration workflow
tests.md — register names in metadata/supported-configurations.json CI: checkInstrumenterModuleConfigurations + config-inversion-linter
tests.md — cover error/exception scenarios @PerfectSlayer reviews
tests.mdcompileOnly vs testImplementation version split @amarziali on #11562
tests.md — prior-version module in testImplementation In-repo pattern: jedis-3.0
muzzle.mdassertInverse=true traps + Pattern A/B @amarziali on #11527
muzzle.md — exclude incompatible major versions @PerfectSlayer on #11717
muzzle.mdskipVersions for malformed release versions Observed failure on jedis jedis-3.6.2

Review feedback addressed

Round 1 of reviews (2026-07-08, from @mcculls and Copilot) surfaced 11 substantive issues. All addressed in 13d6bd32ab and d4600a4624:

  • SKILL.md Step 4 source layout no longer contradicts Step 9.1 (src/test/java/ throughout)
  • naming-conventions.md dropped the invented gRPCInstrumentation example and the buggy sanity-check script
  • tests.md error-test example now uses AgentSpan + span.getTag() (was OpenTelemetry's SpanData)
  • tests.md supported-configurations.json template default is now "true" (was "false")
  • tests.md no-groovy enforcement now references the real workflow (Enforce Groovy Migration), not a made-up bot name
  • context-tracking.md rxjava-2.0 subscribe reference softened to describe the actual matcher pattern
  • advice-class.md onThrowable semantics rewritten per docs/how_instrumentations_work.md:532-552 (the earlier "SAFER" claim was factually wrong)
  • advice-class.md inline note added: StandardCharsets is java.nio.* and forbidden in bootstrap advice

Paired with

DataDog/apm-instrumentation-toolkit#472 — the toolkit's derived copy of this skill. Under the current sync policy, changes flow dd-trace-java → toolkit; the toolkit-side PR is downstream and will be updated after this one merges.

…ep 4.4

Port reviewer-encoded rules from the toolkit's synced derivative
(`apm-integrations` skill in DataDog/apm-instrumentation-toolkit) back
to the canonical `add-apm-integrations` skill.

The toolkit accumulated 21+ numbered reviewer rules during eval research
(commits flowing through PR #11337 jedis-3.0, PR #11562 sparkjava-2.3,
PR #11717 commons-httpclient, PR #11709 feign, and PR #11506 RxJava 3).
These rules encode real failure modes observed during agent-generated
PRs and the fixes reviewers asked for.

Sections added:

- **Step 4.4** — Library category: span-creating vs context-propagation.
  New axis that routes Cat B libraries (reactive, async, executors,
  futures, actors) into a `context_propagation` codegen path producing
  `InstrumenterModule.ContextTracking` instead of span-creating advice.
  Includes the Flowable subscribe(FlowableSubscriber) overload rule
  (hook the framework-internal overload, not the public wrapper).
- **Step 4.5** — Java naming consistency (module-name conventions).

R-rule placements:

- Step 4:  R32 (dir name must end with version OR allowed suffix)
- Step 5:  R13 (no single-type helper class for CallDepthThreadLocalMap)
           R30 (preserve master's integration name when regenerating)
- Step 7:  R15/R16/R17 (single delegate method, not all overloads)
           R33 (no NullPointerException catches; use null-check guards)
- Step 9:  R14 (test error/exception scenarios + spotless)
           R18 (muzzle excludes incompatible majors)
           R19 (latestDepTestImplementation range matches instrumented)
           R20 (Java tests only; no new .groovy files)
           R28 (compileOnly/testImplementation version split rationale)
           R29 (register names in supported-configurations.json)
           R31 (assertInverse only when declared min is true min)
- Step 4.4: R21-R27 (the Cat A vs Cat B classification + Cat B schema)

This PR pairs with toolkit-side PR DataDog/apm-instrumentation-toolkit#472
which adds the same content to the toolkit's synced derivative copy.
Both copies should stay in sync — this is the canonical home.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
@dd-octo-sts

dd-octo-sts Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.99 s 13.94 s [-0.4%; +1.1%] (no difference)
startup:insecure-bank:tracing:Agent 12.90 s 12.99 s [-1.5%; +0.1%] (no difference)
startup:petclinic:appsec:Agent 16.31 s 16.80 s [-7.4%; +1.6%] (no difference)
startup:petclinic:iast:Agent 16.85 s 16.84 s [-0.8%; +0.8%] (no difference)
startup:petclinic:profiling:Agent 16.52 s 16.79 s [-2.7%; -0.5%] (maybe better)
startup:petclinic:sca:Agent 16.83 s 16.68 s [-0.1%; +1.9%] (no difference)
startup:petclinic:tracing:Agent 15.70 s 15.73 s [-6.1%; +5.8%] (unstable)

Commit: d4600a46 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

Two cosmetic passes on the rule encoding ported from the toolkit's
synced derivative copy. Substantive content unchanged.

Pass 1 — strip R-numbering. The R13-R33 numbers are toolkit-internal
traceability tags that tie each rule to a specific reviewer comment on a
specific generated-PR review. They are meaningful to the toolkit-side
eval research workflow but meaningless to readers of the canonical
skill, who have no R1-R12 context here. Removes:

- '#### R<NN> — <title>' prefixes (14 sub-section headings)
- Inline '(R<NN>)', '(R<NN>/R<NN>/R<NN>)' parentheticals on existing
  bullets that already convey the rule in their wording
- A stale cross-reference 'see R29 below' rewritten to 'see
  "Register new integration names"'

The toolkit-side copy keeps the R-numbering — it remains the eval-research
home where the traceability matters. This is a one-way port convention.

Pass 2 — renumber half-steps so the decimals make sense. Was:

  Step 4 → Step 4.4 → Step 4.5 → Step 5

implying missing 4.1, 4.2, 4.3. The original numbering was an artifact
of an earlier toolkit-side draft that had a 4.1-4.4 enumeration which
got collapsed. Renumbered to:

  Step 4 → Step 4.1 → Step 4.2 → Step 5

Main integer steps (1-12) unchanged. Step 7.1 (Multiple advice classes
and @AppliesOn) left as-is since its decimal already makes sense
relative to Step 7.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
Move the 62-line "Step 4.1 – Library category" section from SKILL.md
into references/category-b-context-propagation.md. SKILL.md keeps a
5-line stub linking to the reference file — enough context to know
when to read it, not enough to bury the rest of Step 4.

Preserves all content verbatim; no wording changes. Follows the
existing dd-trace-java skill convention of tracking specific
per-skill files (git add -f, matching the precedent set by
.claude/skills/migrate-groovy-to-java/QUALITY_RULES.md).

Part 1 of a refactor to slim the 794-line SKILL.md into a routing
overview (~250 lines target) with topic-oriented reference files.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
Move two related naming rules from SKILL.md into
references/naming-conventions.md:

- Step 4's module-directory-name rule (must end with version or
  "-common"/"-stubs"/"-iast" suffix)
- Step 4.2's Java filename ↔ class-name matching rule (with the
  sanity-check script)

They belong together because both are enforcement rules for names.
SKILL.md keeps short stubs linking to the reference file.

Preserves all content verbatim. Part 2 of the SKILL.md slim.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
Move Step 5's 103-line body from SKILL.md into
references/instrumenter-module.md, covering:

- @autoservice + narrow-interface preferences (ForSingleType >
  ForKnownTypes > ForTypeHierarchy) with the interface-only API JAR
  exception (JMS, JPA, JDBC, etc.)
- 'Must NOT do' — no static constants for one-shot methods
- instrumentationNames() version-qualified alias rule
- No helper class for single-target CallDepthThreadLocalMap
- Preserve master's integration name on regeneration
- Advanced: grouping multiple instrumentations under one module

SKILL.md keeps Step 5 as a 6-line summary + link. Preserves all
content verbatim; no wording changes. Part 3 of the SKILL.md slim.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
Move Step 7's 149-line body from SKILL.md into
references/advice-class.md. This was the largest single-step body
in the skill and the highest-risk area to get wrong.

Reference file covers:
- Advice method annotations + parameter kinds
- Span lifecycle (enter/exit order)
- onExit resilience to onEnter throwing
- Explicit charset for byte[] to String
- No NullPointerException catches (SpotBugs enforces)
- Single-delegate-method instrumentation (not all overloads)
- @AppliesOn + multiple advice classes
- 'Must NOT do' list (no loggers, no lambdas, no inline=false, etc.)

SKILL.md keeps Step 7 as a summary + link. Preserves all content
verbatim. Part 4 of the SKILL.md slim.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
Move Step 9's 'Instrumentation test' section + all its sub-rules
(no .groovy files, supported-configurations.json registration,
compileOnly/testImplementation version-split rationale,
prior-version-module inclusion) from SKILL.md into references/tests.md.

Muzzle content stays in place for now — it's a separate concern and
gets its own reference file next.

SKILL.md keeps Step 9.1 as a summary + link. Preserves all content
verbatim. Part 5 of the SKILL.md slim.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
Move Step 9's 'Muzzle directives' section + all its sub-rules
(assertInverse gotchas, incompatible-major-version exclusion,
skipVersions for malformed release versions) from SKILL.md into
references/muzzle.md.

SKILL.md keeps Step 9.2 as a summary + link. Preserves all content
verbatim; no wording changes. Part 6 of the SKILL.md slim.

Final state: SKILL.md 794 → ~215 lines, split into 6 topic-oriented
reference files under references/.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
Cosmetic fixup after the section extractions. Three headings lost
their preceding blank line during the awk-based edits — restoring
them so the rendered Markdown reads cleanly.

No content changes.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
@jordan-wong jordan-wong self-assigned this Jul 8, 2026
The 'Category A' / 'Category B' labels came from toolkit-side research
where they were shorthand for the 'target_kind' Pydantic enum values.
They have no meaning in dd-trace-java on their own — a contributor
reading the skill has no context for what 'Category B' refers to.

Replace with the descriptive terms that already exist in dd-trace-java:

- 'span-creating instrumentation' — extends InstrumenterModule.Tracing
- 'context-tracking instrumentation' — extends InstrumenterModule.ContextTracking
  (matches the class name + TargetSystem.CONTEXT_TRACKING enum)

Changes:

- Rename references/category-b-context-propagation.md → references/context-tracking.md
- Rewrite Step 4.1 stub in SKILL.md to drop Category A/B and 'target_kind'
- Rewrite context-tracking.md body from 'Category B target shape' Pydantic-
  field enumeration to 'What a context-tracking instrumentation captures',
  described in Java terms (boundary type, capture/restore points, wrapper
  class, wrapper methods) instead of toolkit Pydantic field names
- Fix advice-class.md's stray 'context-propagation logic' → 'context-tracking
  logic' to match dd-trace-java's TargetSystem.CONTEXT_TRACKING naming

No substantive guidance changed. Reference still points at rxjava-2.0 as
the canonical example.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
Two remaining spots reframed from LLM-agent-workflow perspective to
dd-trace-java human-contributor perspective:

- muzzle.md 'Background' paragraph: 'a typical greenfield generation
  produces...' + 'the agent picks the higher version...' → 'this
  failure mode is common when a module has both a sync and async
  instrumentation class' + 'declaring the higher version as the muzzle
  min...'. Same technical content, no LLM-agent workflow assumption.

- tests.md 'How to discover' step: 'run the sample app' → 'run your
  instrumentation test'. 'Sample app' was ambiguous ('the toolkit's
  sample-app workflow step' vs 'your own test app'); the concrete
  dd-trace-java term is 'instrumentation test'.

No substantive guidance changed. Preserves all rules verbatim.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Ports and restructures the add-apm-integrations Claude skill documentation to incorporate recently reviewer-encoded rules (including a new context-tracking/category-B axis) while splitting the previously-large SKILL.md into a shorter routing overview plus topic-focused reference docs.

Changes:

  • Refactors SKILL.md into a concise step routing guide that links out to detailed reference files.
  • Adds new reference documents covering context-tracking vs span-creating guidance, naming, InstrumenterModule rules, advice rules, tests, and muzzle directives.
  • Updates SKILL step content to point to the new references and codify additional “gotcha” rules (e.g., latestDep version range alignment).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
.claude/skills/add-apm-integrations/SKILL.md Refactored into a shorter step overview with links to new reference docs; adds new Step 4.1/4.2 routing guidance.
.claude/skills/add-apm-integrations/references/context-tracking.md New detailed guidance for context-tracking (async/reactive) instrumentations.
.claude/skills/add-apm-integrations/references/naming-conventions.md New naming rules for module directories and Java class/file naming consistency.
.claude/skills/add-apm-integrations/references/instrumenter-module.md New detailed InstrumenterModule rules (interfaces, helper declarations, naming preservation, etc.).
.claude/skills/add-apm-integrations/references/advice-class.md New detailed advice-class rules and “must/must-not” guidance.
.claude/skills/add-apm-integrations/references/tests.md New testing guidance focusing on Java/JUnit5 and required scenarios + config registration.
.claude/skills/add-apm-integrations/references/muzzle.md New muzzle directive patterns and common failure modes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .claude/skills/add-apm-integrations/SKILL.md
Comment thread .claude/skills/add-apm-integrations/references/naming-conventions.md Outdated
Comment on lines +7 to +9
**Write Java tests (JUnit 5), NOT Groovy/Spock.** dd-trace-java policy
forbids new `.groovy` files — the PR bot (`checkNewGroovyFiles`) rejects any PR containing them.
All new tests must go in `src/test/java/`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 13d6bd32ab — replaced with the real workflow: Enforce Groovy Migration (.github/workflows/enforce-groovy-migration.yaml).

Comment thread .claude/skills/add-apm-integrations/references/tests.md Outdated

**How to identify the right overload:** read the framework source. The public method usually calls a `subscribeActual(...)` or similar protected method that takes the framework-internal interface. If the framework documents one of the overloads as "for internal use only" or marks it `public final`, that's the implementation method — hook it.

**Reference:** dd-trace-java's `rxjava-2.0` hooks `subscribe(Observer)` (the implementation).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 13d6bd32ab — softened to describe the actual matcher (named("subscribe").and(takesArguments(1))) rather than a specific overload.


## Step 4.1 – Span-creating vs context-tracking instrumentation

**Read [Context-Tracking Instrumentation](references/context-tracking.md) before picking instrumentation targets.**

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — PR description now references context-tracking.md (file was renamed in b999e36c36).

Comment on lines +63 to +65
// CORRECT — explicit charset
import java.nio.charset.StandardCharsets;
String cmd = new String(commandBytes, StandardCharsets.UTF_8);

@jordan-wong jordan-wong Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d4600a4624 — added a single-line note that StandardCharsets is java.nio.* and forbidden in bootstrap advice per the "Must NOT do" list below.

@mcculls mcculls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude found four issues in the new reference files — details in the inline comments below.

client.execute(badRequest);
});
List<List<SpanData>> traces = TEST_WRITER.waitForTraces(1);
SpanData span = traces.get(0).get(0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SpanData is an OpenTelemetry SDK type (io.opentelemetry.sdk.trace.data.SpanData), not a dd-trace-java type. TEST_WRITER.waitForTraces() returns List<List<DDSpan>>, so traces.get(0).get(0) is a DDSpan. This example will not compile as written.

Correct pattern:

DDSpan span = writer.get(0).get(0);
assertNotNull(span.getTag("error.type"));
assertNotNull(span.getTag("error.message"));

Error tags in dd-trace-java are plain string keys accessed via span.getTag(String) — there is no getAttributes() / AttributeKey API on DDSpan.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actually for tests AgentSpan should be enough

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 13d6bd32ab — rewrote the example with AgentSpan + span.getTag() per your follow-up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Went with AgentSpan per this. Fixed in 13d6bd32ab.

if (scope != null) scope.close();
}

// SAFER — onThrowable = Throwable.class ensures exit fires even on onEnter exception

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The comment here is factually wrong. onThrowable = Throwable.class on @Advice.OnMethodExit controls whether exit fires when the target method throws — not when the enter advice throws.

Per the project's own docs/how_instrumentations_work.md (lines 532–552):

  • "If the Advice.OnMethodEnter method throws an exception, the Advice.OnMethodExit method is not invoked." — this is unconditional; onThrowable cannot override it.
  • "onThrowable controls whether exit fires when the instrumented method throws an exception."

When enter advice has suppress = Throwable.class, its exception is silently swallowed and the instrumented method continues running normally, so exit fires either way — but onThrowable had nothing to do with it.

Suggested replacement:

// SAFER — onThrowable = Throwable.class ensures exit fires even when the instrumented method throws
//          suppress = Throwable.class on enter ensures enter-advice exceptions do not escape
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For own knowledge, don't necessarily add in to skill

  • typically used for clearing scopes
  • maybe not used if: recording some fact about method, if fails dont want to record that fact

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 13d6bd32ab — rewrote section with correct semantics + citation to docs/how_instrumentations_work.md:532-552.

{
"version": "A",
"type": "boolean",
"default": "false",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The "default": "false" here is wrong for the typical integration. Of the DD_TRACE_*_ENABLED keys in metadata/supported-configurations.json, ~83% of non-analytics enabled-flags default to "true" (jedis, okhttp, kafka, spring-web, etc.). "false" is reserved for integrations that override defaultEnabled() (OpenTelemetry, Hazelcast, sparkjava, etc.).

Hardcoding "false" here means a developer following the template registers every new integration as disabled by default. The checkInstrumenterModuleConfigurations linter only checks key presence — not the accuracy of the default value — so this passes CI silently.

Suggested fix: change the template to "default": "true" and add a note:

"default": "true"   // use "false" only if the module overrides defaultEnabled()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 13d6bd32ab — default is now "true"; added note that "false" is reserved for modules that override defaultEnabled().

# Every public class declaration must match its filename
cd dd-java-agent/instrumentation/$framework/$framework-$version/src/main/java
for f in $(find . -name '*.java'); do
CLS=$(grep -oE 'public (final )?(abstract )?class [A-Za-z0-9_]+' "$f" | awk '{print $NF}')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The grep pattern public (final )?(abstract )?class [A-Za-z0-9_]+ only matches the class keyword, so it produces no output for public enum Foo, public interface Bar, or public @interface Baz. For any such file, CLS is empty and [ "" = "Foo" ] fires, printing a spurious MISMATCH.

These files are common in the instrumentation tree — e.g. LogHandler.java (public interface), ParameterCollector.java (public interface). Running this script would produce noise on every one of them.

A secondary issue: files with a nested public static class cause grep -oE to emit multiple lines, making CLS a newline-joined string that never equals EXPECTED.

Suggested fix:

for f in $(find . -name '*.java'); do
  CLS=$(grep -oE '\bpublic\b[[:space:]]+(final[[:space:]]+|abstract[[:space:]]+)?(class|enum|interface|@interface)[[:space:]]+[A-Za-z0-9_]+' "$f" | head -1 | awk '{print $NF}')
  EXPECTED=$(basename "$f" .java)
  [ -z "$CLS" ] || [ "$CLS" = "$EXPECTED" ] || echo "MISMATCH: $f declares '$CLS'"
done

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 13d6bd32ab — dropped the sanity-check script entirely; javac already enforces filename ↔ public-class-name.

@jordan-wong jordan-wong Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we discussed just leaving a simple 1 line comment @mcculls but this actually seems fine to drop entirely IMO

@jordan-wong

Copy link
Copy Markdown
Contributor Author

For some context, how I create these prompt updates goes like:
-> have doc in toolkit research branch to track items/ rules to add to skills
-> at each step of evaluation process for generated PR process:

  • collect feedback
  • make changes if fix needed
  • add rules to running list if fix seems generalizeable

Evaluation process:
Generate PRs with toolkit -> CI test failures -> LLM review -> human review feedback

jordan-wong and others added 2 commits July 8, 2026 09:20
Nine fixes from Copilot bot + @mcculls review comments:

SKILL.md
- Step 4 source layout: 'src/test/groovy/ — Spock tests' → 'src/test/java/
  — JUnit 5 tests'. Contradicted Step 9.1's Java-only policy. (Copilot)

references/tests.md
- Rewrite the error-test example: 'List<List<SpanData>> traces = ...' used
  OpenTelemetry's SpanData type (won't compile against dd-trace-java's
  TEST_WRITER, which returns List<List<DDSpan>>). Now uses AgentSpan and
  span.getTag() per mcculls's guidance that AgentSpan is enough for tests.
- Replace 'checkNewGroovyFiles' (unverifiable bot name) with the real
  workflow: 'Enforce Groovy Migration'
  (.github/workflows/enforce-groovy-migration.yaml). Both places.
- Default value in supported-configurations.json: change 'false' to 'true'
  per mcculls — ~83% of typical integrations default to true; 'false' is
  reserved for modules that override defaultEnabled() (OpenTelemetry,
  Hazelcast, sparkjava). Add a note calling out the branching.

references/naming-conventions.md
- Remove gRPCInstrumentation as an example — it doesn't exist in the
  codebase; the gRPC integration uses Grpc* (GrpcClientDecorator etc).
  Reframe the section to acknowledge acronym casing is not uniform
  across dd-trace-java and to defer to a reference instrumentation.
  (Copilot)
- Drop the sanity-check bash script entirely. mcculls flagged that its
  regex only matched 'class', missing enum/interface/@interface, and
  would produce false MISMATCH lines for any such file (LogHandler.java,
  ParameterCollector.java, etc.).

references/advice-class.md
- Rewrite the 'onExit resilient to onEnter throwing' section — the
  claim that 'onThrowable = Throwable.class ensures exit fires even on
  onEnter exception' was factually wrong. Per
  docs/how_instrumentations_work.md:532-552, 'if the OnMethodEnter
  method throws an exception, the OnMethodExit method is not invoked' —
  unconditionally; onThrowable cannot override it. onThrowable controls
  exit-on-target-method-throw, not exit-on-enter-throw. (mcculls)
- Add inline note that java.nio.charset.StandardCharsets is a java.nio.*
  type and forbidden in bootstrap instrumentations (per the same file's
  Must NOT list). In bootstrap advice, use the string charset name
  ('UTF-8') instead. (Copilot)

references/context-tracking.md
- Soften the 'rxjava-2.0 hooks subscribe(Observer)' statement. The
  module's actual matcher is named('subscribe').and(takesArguments(1)),
  matching any single-arg subscribe overload with the argument typed
  as the base callback interface. Direct the reader at the module
  source instead of copying overload names. (Copilot)

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
@datadog-official

datadog-official Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 56.92% (-0.04%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: d4600a4 | Docs | Datadog PR Page | Give us feedback!

Copilot's suggestion was 'add an explicit note here'; the initial fix
was a full paragraph. Trimming to a single-sentence pointer since the
Must NOT list already carries the details.

Signed-off-by: Jordan Wong <jordan.wong@datadoghq.com>
@jordan-wong jordan-wong requested a review from Copilot July 8, 2026 15:12
@jordan-wong jordan-wong changed the title skill(add-apm-integrations): R13-R33 + Cat B (context-propagation) rules skill(add-apm-integrations): add rules from recent reviews + progressive-disclosure refactor Jul 8, 2026
@jordan-wong jordan-wong changed the title skill(add-apm-integrations): add rules from recent reviews + progressive-disclosure refactor skill(add-apm-integrations): add rules from recent reviews + refactor into sub references Jul 8, 2026
@jordan-wong jordan-wong marked this pull request as ready for review July 8, 2026 15:15
@jordan-wong jordan-wong requested a review from a team as a code owner July 8, 2026 15:15
@jordan-wong jordan-wong requested a review from ygree July 8, 2026 15:15
@dd-octo-sts

dd-octo-sts Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Hi! 👋 Thanks for your pull request! 🎉

To help us review it, please make sure to:

  • Add at least one type, and one component or instrumentation label to the pull request

If you need help, please check our contributing guidelines.

@jordan-wong jordan-wong requested review from mcculls and removed request for ygree July 8, 2026 15:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

**Read [Naming Conventions](references/naming-conventions.md) § "Java naming consistency".**

### Must NOT do in InstrumenterModule
Filename and `public class` name MUST match character-for-character (including acronym casing). Use one canonical string everywhere: filename, class decl, `import static`, `ClassName.member` references. The reference file has a sanity-check script — run it before declaring done.
Comment on lines +7 to +10
**Write Java tests (JUnit 5), NOT Groovy/Spock.** dd-trace-java policy
forbids new `.groovy` files — the `Enforce Groovy Migration` workflow
(`.github/workflows/enforce-groovy-migration.yaml`) rejects any PR containing them.
All new tests must go in `src/test/java/`.
Comment on lines +12 to +15
- JUnit 5 test class in `src/test/java/datadog/trace/instrumentation/<framework>/`
- Verify: spans created, tags set, errors propagated, resource names correct
- Use `TEST_WRITER.waitForTraces(N)` for assertions
- Use `runUnderTrace("root", () -> { ... })` for synchronous code (Java lambda, not Groovy closure)
Comment on lines +19 to +32
```java
// Example error test (Java)
@Test
void testExceptionSetsErrorTags() throws Exception {
assertThrows(SomeException.class, () -> {
// trigger operation that throws
client.execute(badRequest);
});
List<List<AgentSpan>> traces = TEST_WRITER.waitForTraces(1);
AgentSpan span = traces.get(0).get(0);
assertNotNull(span.getTag("error.type"));
assertNotNull(span.getTag("error.message"));
}
```
Comment on lines +46 to +47
@ExtendWith(AgentJUnit5Extension.class)
public class Jedis3ClientTest extends AgentInstrumentationTest {
}
```

**Pattern B — Bounded range** (your instrumentation supports `[minVersion, maxVersion)` and an existing sibling module covers `[maxVersion, ∞)`). **Do NOT use `assertInverse = true`** — it can pick boundary versions inside your declared range as inverse-test targets, causing `muzzle-AssertFail-...` failures:
MUZZLE PASSED JedisInstrumentation BUT FAILURE WAS EXPECTED
```

Instead, declare BOTH the pass range AND the explicit fail ranges that bound it:
```
**Read [Muzzle Directives](references/muzzle.md).**

Two valid patterns: open-ended range (`[$min,)` with `assertInverse = true` only when the true min is verified) or bounded range (`[$min,$max)` with explicit `fail { versions = "[,$min)" }` — no `assertInverse`). Muzzle range must exclude incompatible major versions when the same `group:module` republishes with a rewritten API. Library-specific quirks (malformed release versions like `jedis-3.6.2`) require `skipVersions` — search adjacent modules for these before declaring new ranges.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d4600a4624

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +41 to +50
### instrumentationNames() must include a version-qualified alias

```java
@Override
public String[] instrumentationNames() {
// WRONG — only generic name
return new String[]{"jedis"};

// CORRECT — generic + version alias
return new String[]{"jedis", "jedis-3.0"};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Put version aliases in InstrumenterModule super args

For new version-specific enable/disable names, this needs to point agents at the InstrumenterModule constructor (super("jedis", "jedis-3.0")), not instrumentationNames(): InstrumenterModule exposes the configured integration names through its constructor/names list, while instrumentationNames() is a decorator method whose strings are checked as analytics keys by CheckDecoratorAnalyticsConfigTask. Following this example either adds a non-overriding method to the module or puts jedis-3.0 on the decorator, which will not make DD_TRACE_JEDIS_3_0_ENABLED control the module and will instead force unrelated analytics metadata.

Useful? React with 👍 / 👎.

Comment on lines +14 to +15
- Use `TEST_WRITER.waitForTraces(N)` for assertions
- Use `runUnderTrace("root", () -> { ... })` for synchronous code (Java lambda, not Groovy closure)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the Java instrumentation test harness APIs

These Java-test bullets still reference the Groovy InstrumentationSpecification API: TEST_WRITER is not exposed by the Java JUnit harness, and Java tests in this repo extend datadog.trace.agent.test.AbstractInstrumentationTest, using assertTraces/writer plus helpers such as TraceUtils.runUnderTrace. If agents follow this text for new src/test/java instrumentation tests, the generated tests will not compile or will be written against the wrong harness.

Useful? React with 👍 / 👎.


### onExit handling when the target method throws

The `onThrowable = Throwable.class` attribute on `@Advice.OnMethodExit` controls whether the exit advice fires when the **instrumented target method** throws. Set it to `Throwable.class` (or omit it — this is the default) if you need to close the scope / finish the span regardless of whether the target method returned normally or threw.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require onThrowable when finishing spans

When a span-finishing exit advice omits onThrowable, Byte Buddy does not call it if the instrumented method exits exceptionally (the OnMethodExit javadoc says the default skips exceptional termination: https://javadoc.io/doc/net.bytebuddy/byte-buddy/1.10.2/net/bytebuddy/asm/Advice.OnMethodExit.html). Following this sentence would leak the active scope/span on exceptions, so keep requiring onThrowable = Throwable.class for non-constructor span-finishing advice.

Useful? React with 👍 / 👎.


### Register new integration names in `metadata/supported-configurations.json`

Every new integration name in your module (whether from `super("foo-X.Y")` in `InstrumenterModule`, or from `instrumentationNames()` in the decorator) MUST have a corresponding entry in `metadata/supported-configurations.json` at the repo root. The `dd-gitlab/config-inversion-linter` CI job fails otherwise.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Register decorator names as analytics keys

checkInstrumenterModuleConfigurations derives DD_TRACE_<NAME>_ENABLED only from InstrumenterModule super(...) args, while decorator instrumentationNames() are validated by checkDecoratorAnalyticsConfigurations as DD_TRACE_<NAME>_ANALYTICS_*. When a decorator has a name that is not also a module name, following this sentence adds the wrong _ENABLED key and still leaves CI failing for the missing analytics keys.

Useful? React with 👍 / 👎.

Comment on lines +71 to +74
// CORRECT — use CallDepthThreadLocalMap directly with the Advice or Decorator class as key
if (CallDepthThreadLocalMap.incrementCallDepth(GsonInstrumentation.class) > 0) return;
// ... in exit:
CallDepthThreadLocalMap.reset(GsonInstrumentation.class);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid using module classes as call-depth keys

This “CORRECT” example uses GsonInstrumentation.class from inside advice, but instrumentation/module classes are not helper-injected into the application classloader, and this file also says advice must not reference the InstrumenterModule class. If an agent copies this pattern into inlined advice, the transformed application method can retain a class literal for a non-visible Datadog instrumentation class; use a target library class or declared helper/decorator class as the call-depth key instead.

Useful? React with 👍 / 👎.

For complex frameworks with multiple version-specific or feature-specific instrumentations, you can group them under a single `InstrumenterModule` (file ending in `Module.java`). The module class:

- Must extend a `TargetSystem` subclass and have `@AutoService(InstrumenterModule.class)`
- Must implement `typeInstrumentations()` returning an array of instrumentations

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return a List from grouped modules

InstrumenterModule.typeInstrumentations() returns List<Instrumenter> in this codebase, and existing grouped modules override that signature with a List, not an array. Following this bullet literally makes the grouped module fail to override the method (or fail compilation), so the guidance should say to return a List<Instrumenter>.

Useful? React with 👍 / 👎.

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.

3 participants