- name
- write-changelog
- description
- 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.
# Writing changelog fragments
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
```markdown
<!-- 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
```markdown
<!-- 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
```markdown
<!-- 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
```markdown
<!-- 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.
```markdown
<!-- 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
```markdown
<!-- 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
```markdown
<!-- 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
```markdown
<!-- 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")
```markdown
<!-- 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
```markdown
<!-- 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
Ver en GitHub