Writing changelog fragments

SkillDev tools

Use when a change in this repo needs a customer-facing changelog entry — e.g. "add a changelog entry" — or when an approved PR that changes user-visible behavior in lib/, ext/, or docs/GettingStarted.md is about to merge.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Writing changelog fragments skill

What this skill tells your AI

The instructions your AI receives, as published by datadog/dd-trace-rb in .claude/skills/write-changelog/SKILL.md and read by ahel’s review.

READ unreleased/README.md FIRST — it is the human-facing description of the fragment system and its fields. This skill is the agent-facing checklist.

A fragment may be written as soon as the PR exists, but its grounding is only final when the diff is: re-ground the message against the final diff when the PR is complete and approved, right before merging — grounding and triage see the complete change only there.

The customer

Every message targets one reader: a Ruby developer using this gem in their application, scanning CHANGELOG.md to decide whether an upgrade affects them. They read for what changes in their application — what they observe, do, or get. The exact version they run is their context.

Core principles

  • ALWAYS write for the customer, not the diff
  • ALWAYS lead with the customer effect; supporting detail follows
  • ALWAYS scope who is affected — versions, platforms, triggers, workloads — whenever the scope decides it
  • NEVER narrate the implementation Scope names what the customer runs; narrative names what the code does

Grounding

Read the diff and triage first; the answers drive every later step:

  • Effect: what does this PR make true when it lands? If the effect arrives only in a later change (groundwork, flag plumbed but off), there is nothing to write yet

  • Count: one change or several distinct effects? Several → ASK the user to confirm the split before writing several fragments; NEVER bundle several effects into one

    <!-- Bad: two effects bundled into one fragment -->
    Add support for Bundler deployment mode (`bundle install --deployment`) and report UI-oriented injection results.
    
    <!-- Good: one effect stated whole; the PR's other effects each get their own fragment -->
    Add support for Bundler deployment mode (`bundle install --deployment`).
    
  • Product: which product each effect belongs to — by effect, not code location (a core fix to a profiler crash is Profiling); diffuse core-wide → Core. Closed list: ReleasePrep::Fragment::PRODUCTS (tasks/lib/release_prep/fragment.rb); the integration name (Redis) goes in the message, not the product

  • Type: Added (new capability), Changed (behavior change), Fixed (bug fix); performance changes are Changed, or Fixed when they restore performance an earlier version had

Then keep every written claim grounded:

  • Verify identifiers (settings, classes, methods, env vars, gems) against the hunks, not recalled conventions
  • Verify versions against the diff (gemspec, Matrixfile, CI), not ecosystem memory
  • Measured numbers: the diff, its benchmark output, or the author's reported result — a benchmark's output often lives only in the PR; no source there, and the claim stays directional
  • Verify behavior against the diff's tests — no test, no behavioral claim
  • A reproducible defect with no test: ASK the PR to add the test; the behavioral claim waits for it
  • A PR that merges without the test grounds its entry in what inspection establishes — the claim caps there, and the review thread notes the missing test
  • When a deterministic test cannot reproduce the defect — it depends on timing or uncontrolled external state — verification falls back to inspection: the change the diff adds is the evidence, and the message claims no more than it establishes
  • ALWAYS drop or weaken a claim that traces to nothing; NEVER hedge it

Deciding

  • ALWAYS add a fragment for new features, behavior changes, and customer-affecting bug fixes
  • NEVER add a fragment for internal refactors, test-only, CI/tooling, or docs outside docs/GettingStarted.md
  • SHOULD add when unsure — a reviewer can delete an entry; a missing one leaves customers unaware

Creating the fragment

  • ALWAYS write against a real PR number; with no PR yet, open a draft first — nothing checks the number mechanically

  • ALWAYS update the existing fragment for this effect; NEVER add a second one for the same effect. Fragments for other effects stay untouched

  • ALWAYS generate the scaffold with bundle exec rake unreleased:new; NEVER create a blank file by hand

    The placeholders name what each field needs; filled-in references live in unreleased/examples/.

  • ALWAYS set author to the external contributor's @-prefixed GitHub handle; NEVER set author for a Datadog contributor

The floor and the standard

bundle exec rake unreleased:lint and unreleased:vale enforce a basic mechanical floor and it will keep expanding. The floor never proves an entry good: passing it proves the entry checkable. Every rule below is the standard, stated on its own terms; some overlap the floor, none lean on it.

Writing the message

The rules follow in drafting order. When the structure will not fit the caps, keep the customer effect, its scope, and any access point or escape hatch — the compressible rest is the explanatory detail, never the actionable.

  • Structure by type — the reader's question differs:

    • Fixed: ALWAYS name the symptom they recognize, then the trigger they can perform or observe (a call they make, a setting they use); when nothing is observable, bottom out at what the evidence establishes, and NEVER below it, into internal sequencing

      <!-- Bad: the trigger named, but no symptom to recognize -->
      Fix `Process.spawn` when passing an environment Hash.
      
      <!-- Good: the symptom they recognize + the trigger they perform -->
      Fix `TypeError` from `Process.spawn` when passing an environment Hash.
      
    • Added: ALWAYS name the capability, then the access point — the setting/API that gets it for them

      <!-- Bad: the capability stated, but no access point to enable it -->
      Show class and module names in profiler stack frames (`Foo::Bar#baz` instead of `baz`), making hot methods easier to identify.
      
      <!-- Good: capability + example + why it helps + the exact access point -->
      Show class and module names in profiler stack frames (`Foo::Bar#baz` instead of `baz`), making hot methods easier to identify; enable it with `DD_PROFILING_EXPERIMENTAL_SHOW_CLASSES_ENABLED=true`.
      
    • Changed: ALWAYS name the new behavior and the escape hatch when one exists; SHOULD follow with why it matters

      <!-- Bad: the new behavior and why, but no way back -->
      Move the gem's diagnostic logs from stdout to stderr, so stdout stays clean for application output.
      
      <!-- Good: new behavior + why it matters + escape hatch -->
      Move the gem's diagnostic logs from stdout to stderr, so stdout stays clean for application output; restore the old default with `c.logger.instance = Datadog::Core::Logger.new($stdout)` in `Datadog.configure`.
      
  • ALWAYS state what changed, in customer terms; NEVER code-review jargon ("refactored", "cleaned up") or internal file names.

    <!-- Bad: code-review terms lead — jargon, file name, the customer claim buried at the end -->
    Set `Tracing::Metadata::Ext::TAG_KIND` on spans in the `ActiveRecord` `sql` event handler (`events/sql.rb`) to fix missing peer tags for database queries.
    
    <!-- Good: customer framing, grounded, code spans -->
    Fix missing peer tags for database queries traced through `ActiveRecord`.
    
  • ALWAYS open with a present-tense imperative verb (Add, Fix, Support, Improve, ...); NEVER the past tense ("Fixed a crash..."), process-speak ("This PR fixes..."), or subject-first prose ("The gem now supports...", "Also fixes...") — the entry states what the new version does for the customer, not a report of work done

    <!-- Bad: the past tense reads as a report of work done, not what it does -->
    This PR fixes missing peer tags for database queries traced through `ActiveRecord`.
    
    <!-- Good: the present-tense imperative opens the entry -->
    Fix missing peer tags for database queries traced through `ActiveRecord`.
    
  • ALWAYS end the message with terminal punctuation — the entry is a sentence, not a fragment

  • ALWAYS open on the customer's observable delta; the mechanism, scope, and numbers follow it

    <!-- Bad: the mechanism opens; the customer's delta never leads -->
    Enforce a process-wide rate limit across all probes: with multiple probes set, they can emit less than their individual limits allow — combined output is capped at 20 snapshots/s and 5000 log events/s per process.
    
    <!-- Good: the customer's observable delta leads, the numbers follow -->
    Cap probe output process-wide: with multiple probes set, they can emit less than their individual limits allow — combined output is capped at 20 snapshots/s and 5000 log events/s per process.
    
  • ALWAYS make the CamelCase code-span call: span what names the code the customer runs (ActiveRecord), leave product names bare in prose (Bundler)

  • NEVER repeat the product verbatim — with product AppSec, "Add AppSec detection..." says it twice; lowercase technical phrasing ("GC profiling") is fine

    <!-- Bad: the product said twice -->
    Add AppSec detection of response splitting.
    
    <!-- Good: the product already renders beside the entry -->
    Add detection of response splitting.
    
  • ALWAYS name exact versions and platforms when they decide who is affected — NEVER vague quantifiers ("recent", "newer")

    <!-- Bad: vague version -->
    Disable live heap size profiling on recent Ruby versions due to incompatibility.
    
    <!-- Good: exact version -->
    Disable live heap size profiling on Ruby 4.0 due to incompatibility.
    
  • SHOULD back performance claims with measured numbers from the PR's own evidence (Grounding); a claim with no measured number there stays directional

    <!-- Bad: the vague quantifier stands in for the directional claim -->
    Reduce profiler overhead significantly for applications with many idle or blocked threads by skipping samples that would carry no new information; skipped threads are still reported each period.
    
    <!-- Good: directional with scope — the PR's evidence carries no measured number -->
    Reduce profiler overhead for applications with many idle or blocked threads by skipping samples that would carry no new information; skipped threads are still reported each period.
    

Finishing loop

Run the three steps in order; ANY revision restarts the loop from step 1. Done when a pass makes no revision:

  1. bundle exec rake unreleased:lint and bundle exec rake unreleased:vale — fix every reported violation
  2. bundle exec rake unreleased:render — check the rendered entry against every Core principle and every Writing rule in turn, and revise; done when each one is accounted for
  3. Re-run the Grounding triage against the final diff — effect, count, product, and type, then every identifier, version, number, and behavioral claim traced to a specific hunk, benchmark output, or test; drop or weaken each one that traces to nothing. A count that grew means a new effect: the ASK re-fires and it gets its own fragment

Signals

GitHub stars
416
Forks
417
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
write-changelog
Source
github.com/datadog/dd-trace-rb