Éditer le changelog ClickHouse

Édite les changelogs de version ClickHouse générés automatiquement pour les transformer en format final pour CHANGELOG.md, en appliquant les conventions des mainteneurs.

Spar Skills Guide Bot
DocumentationAvancé
0031/08/2026
Claude Code
#changelog#clickhouse#release-notes#editing

Recommandé pour


name: edit-changelog description: Edit an auto-generated ClickHouse release changelog into the form that gets committed to CHANGELOG.md. Use when the user has the output of utils/changelog/changelog.py and wants it cleaned up and re-categorized for a release. argument-hint: "[path-to-autogenerated-changelog.md]" disable-model-invocation: false allowed-tools: Bash, Read, Edit, Write, Grep, Glob, Task

ClickHouse Changelog Editing Skill

What this skill does

The autogenerator (utils/changelog/changelog.py) converts every PR description in the release range into a bullet under the category the author picked. The maintainer then heavily edits that output before it lands in CHANGELOG.md. This skill applies those edits.

The patterns below were derived by diffing the autogenerated commit and the following "edited" / "Cleanup" commit for releases 25.2, 25.3, 25.5, 25.7, verifying against PR descriptions for 26.4, and from the manual edits the maintainers still had to make after the skill-assisted passes for 26.6 and 26.8. Don't invent new conventions — if a pattern isn't here, leave the entry alone.

Arguments

  • $0 (optional): path to the auto-generated changelog file (the output of utils/changelog/changelog.py --output=...). Edit this file in place.

If $0 is omitted, default to editing the most recent release section in CHANGELOG.md. Identify it by the first ### <a id="..."></a> ClickHouse release X.Y, ... heading, and treat the slice from that heading up to the next ### <a id= heading as the input. This is the common case after the maintainer has already pasted the autogenerated output into CHANGELOG.md.

Workflow

  1. Read the input file. Confirm it has the autogenerator's structure: a ### ClickHouse release ... FIXME ... header followed by #### <Category> sections of * <entry>. [#NNN](...) ([Author](...)). bullets.
  2. Make the edits described under "Edits" below, in the order listed.
  3. After each substantive change, show the user a short summary (one line per non-trivial edit) — don't dump the whole diff.
  4. When done, do not commit or paste into CHANGELOG.md automatically. Tell the user the file is ready; they will paste it into CHANGELOG.md themselves and commit.

Edits, in order

Each edit type below was observed at least twice across the surveyed releases. For real before/after examples, consult the diffs listed at the bottom under "How to use the surveyed past releases".

Retention rule — read this before deleting anything. An entry that the autogenerator put under one of the real categories (Backward Incompatible Change, New Feature, Experimental Feature, Performance Improvement, Improvement, Bug Fix) is never deleted. Rewrite it (§5), merge it into another bullet (§7), move it to another category (§6) — but its [#NNNNN](...) link stays in the file. Deletion is confined to three places: the NOT FOR CHANGELOG / INSIGNIFICANT and NO CL ENTRY bullets that carry no user-visible change (§3), the Build/Testing/Packaging Improvement entries that are pure CI plumbing (§4), and an entry that a revert cancels out — and that no later revert of that revert brings back (§2). "This looks unimportant" is not a reason to drop a real entry — an entry lost here is lost for good, because the changelog is generated once per range.

The NightlyChangelog CI job enforces exactly this: a run whose edit drops such a link fails with Entries disappeared in the edit without a matching revert, and the raw entries stay unedited until someone fixes it. That was the state of the 26.8 changelog for twelve days in August 2026.

1. Release header

The autogenerator emits:

### ClickHouse release {TO_REF} ({sha11}) FIXME as compared to {FROM_REF} ({sha11})

Replace it with:

### <a id="NNN"></a> ClickHouse release X.Y[ LTS], YYYY-MM-DD. [Presentation](https://presentations.clickhouse.com/YYYY-release-X.Y/), [Video](https://www.youtube.com/watch?v=...)

Where NNN is the version with dots removed (26.4264). The X.3 and X.8 releases of each year are LTS — add the LTS marker for those automatically (26.8v26.8 LTS); for any other version add it only if the user says so. The same LTS marker goes into the TOC line. If the presentation and video links aren't known yet, leave a FIXME placeholder and tell the user to fill them in — don't invent URLs. While the release is still being prepared, the header keeps a trailing , FIXME (in progress) after the date (e.g. ClickHouse release 26.8 LTS, 2026-08-27, FIXME (in progress)); the finalization commit removes it.

The TOC at the top of CHANGELOG.md also needs a new line; only do this if the user is editing CHANGELOG.md directly.

2. Resolve #### NO CL ENTRY against the rest of the changelog

These are revert PRs (Revert "...") that the autogenerator includes because the revert PR has no Changelog entry. Walk every bullet in this section. For each:

A revert is not always in this section. The autogenerator renders the author's own Changelog entry under the author's own Changelog category, so a revert whose author filled both in appears as an ordinary bullet — under Improvement, reading "Disable the setting again", with nothing to give it away. When a bullet undoes something you have seen in this release, check its PR (gh pr view <N> --json title,body; the title of a revert says Revert, and GitHub adds a Reverts owner/repo#NNNNN line to the body) and treat it by the rules below. Such a revert's own bullet is deleted with the entry it cancels, exactly like one from this section — the "never delete a real entry" rule does not protect it, because it is not a change that ships. A revert that undoes something of this release and something from an earlier one at the same time is the exception: the second half is user-visible, so it keeps an entry of its own (case 5 below) — its link appended to the entry it re-applied is an annotation on that entry, not a substitute for its own.

"Something of this release" includes a PR whose entry was pruned earlier under §3 or §4: the change was still made and undone inside the range, so the revert has no user-visible effect either. It does not include a PR whose entry is already in a released section — merged into the last release branch after this cycle started, so users have the behaviour — and reverting that is case 5.

Deleting the entry is only half of it — the revert must not be left behind as an entry of its own either, whatever category the autogenerator gave it. The user should see no trace of either side. The one place such a PR still appears is as the appended link on an entry it re-applied (§2.5), where it records the re-apply rather than describing a change.

  1. Read the title of the revert PR (gh pr view <N> --json title,body) to identify which earlier PR it reverts. Most reverts have a title of the form Revert "<original PR title>" or Revert #NNNNN, and GitHub puts a Reverts owner/repo#NNNNN line into the body.

  2. Search the rest of the in-progress changelog for the matching entry by PR number, title, or topic.

  3. Check whether this revert is itself reverted before you conclude anything — by a Revert "Revert "...""" title elsewhere in the section, or by a body containing Reverts owner/repo#<this PR>. If it is, the change ships in this release: go to step 6 and leave the original entry in place. A three-PR chain (change, revert, revert of the revert) sits entirely inside one range often enough to matter, and when the release is edited incrementally, the chain is spread over several of those increments (step 6).

  4. If the original PR is in the same release range and stays reverted: delete that entry from its category. Do not keep the revert PR as a separate bullet — the user should see no trace of either.

  5. If the original PR shipped in an earlier release and the revert is meant to be visible to users: rewrite the revert into a normal entry under the appropriate category (often Bug Fix or Backward Incompatible Change), describing the user-visible effect of the revert.

  6. If the revert PR is itself a revert-of-revert (i.e. it re-applies a change that was previously reverted): keep the original entry, append the second revert's PR/author link to it so both PR numbers are recorded, and delete the intervening revert from the section.

    Real example from 26.8: #109946 fixed the propagation of settings in accurateCastOrDefault, #114911 reverted it, #114912 reverted that revert. All three are in the range. The result is one Bug Fix bullet — #109946's entry with #114912's link appended — and no trace of #114911. Deleting #109946 "because it was reverted" drops a fix that ships in the release.

    When a release is edited in daily increments, the three PRs arrive on three different days: the entry was integrated on the first day, deleted on the second, and the third brings only the revert of the revert. The original entry is then not in the file to be kept — it has to be re-added from the text it had when it was deleted, which the caller supplies (the NightlyChangelog job lists such entries with their previous text and category under "Entries to restore"). If nothing supplies it, reconstruct the entry from the original PR with gh pr view <N> --json title,body; do not leave the release without it.

    A bullet that covered several PRs comes back as one bullet carrying all of them, not as one bullet each — splitting it undoes the merge of §7. That includes the PRs of that bullet which never left: re-adding the entry beside the surviving bullet duplicates the prose even though no PR link repeats, so merge it into that bullet instead. A revert can be one of those PRs — a revert of an earlier release is a normal entry (case 5) and can have been merged like any other; what is not a PR of the bullet is the link a previous restoration appended to record a re-apply. And if only some of those PRs were re-applied, leave the others' links off: their reverts still stand, so those changes are not in the release, even though the recorded text attributes them.

    The link of the PR that re-applied the change goes on the restored entry, as in the main rule above: its own revert bullet is deleted, so that link is the only trace of the re-apply left in the release. A change can be taken out and put back more than once in a cycle — #109946 out by #114911, back by #114912, out by #115500, back by #116000 — and then every PR that put it back belongs on the entry, not just the first. The ones that took it out leave no trace.

    One revert can revert several PRs at once. When such a revert is itself reverted, every entry it brings back records that revert-of-revert, so the same link appears on each of them — that is not the same as one entry written twice. And a revert rewritten into a visible entry (case 5 above) is an entry like any other: if it is deleted and later restored, it takes the link of the PR that restored it too.

    An entry the autogenerator had filed under NOT FOR CHANGELOG or as CI plumbing and that was never added to the release is offered back, not required: §3 and §4 were free to prune it, so decide again whether it describes a user-visible change. One that had already been added is required — that decision was made when it went in. What matters is that the choice is possible — the entry is quoted for you because nothing in the file remembers it any more.

    When the restoration merges into a surviving bullet, it lands under that bullet's current category — which a later edit may have moved. Otherwise, restore it under the category it was in, which is not necessarily the one its PR declares: the entry may have been promoted out of NOT FOR CHANGELOG (§3) or moved (§6) by the edit that first added it, and re-deriving the category from the PR would undo that.

    If the deleted entry shared a bullet with other PRs (§7) and those are still in the in-progress section, that bullet is still there too: append the missing PR link to it instead of adding a second bullet. The same PR appearing in an already-released section further down is not that bullet. No PR may end up attributed twice within one section — one [#NNNNN](...) ([Author](...)) per PR.

    A restored PR needs its own attribution, [#NNNNN](...) ([Author](...)). A bare [#NNNNN](...) link inside somebody else's bullet — a This closes reference, a follow-up named in prose — is not that PR's entry and does not count as restoring it.

  7. After processing, delete any leftover bullets and the section header itself.

The goal is that the final changelog reflects the net effect on the release: a PR that landed and then got reverted shouldn't appear at all, and one whose revert was itself reverted appears with every PR that put it back. Both halves are enforced by the NightlyChangelog job — keeping a reverted entry fails it just as dropping a real one does.

Reverts can postdate your input. The changelog is built incrementally, so a PR whose entry is already in the file can be reverted after the range your input was generated from — no NO CL ENTRY bullet will ever tell you about it. On the final pass, search the merge history since the last generation run for Revert-titled PRs (git log --oneline --grep='^Revert' <last-generated-sha>..origin/master or gh pr list --state merged --search 'Revert') and delete the entries of anything reverted. In 26.8, the entry for #111973 had to be removed by hand (#116504) because the revert landed after the entry was written. If the change was reverted but a revert-of-revert is expected before the release, keep the entry and prepend TODO: wait for the revert-of-revert. instead of deleting it (see §9 for the TODO flag convention).

3. Drop entire #### NOT FOR CHANGELOG / INSIGNIFICANT section, but rescue user-visible entries

Walk every bullet in this section. For each:

  • If the entry mentions a user-visible behaviour (function/setting name, a Fix for a real bug, a perf change with a number, a new column in a system table, etc.) — promote it into the appropriate category (use the rules in §6 to pick the category). Don't strip the content; only strip developer-internal preambles like "fix msan ...", "ci: ...". If after stripping there is no real user-facing description, drop the entry instead of promoting an empty one.
  • Otherwise — delete it.

Then delete the section header itself. This section, NO CL ENTRY and the CI plumbing of §4 are the only places where entries are deleted; see the retention rule above.

This closes / Closes #N / Fixes #N entries

These are valuable — they tie the change to the issue tracker. Keep them, don't strip. Apply this shape:

  • The issue reference belongs at the end of the entry, after the description text, not at the start.
  • The reference must be a markdown link to the issue (Closes [#NNNNN](https://github.com/ClickHouse/ClickHouse/issues/NNNNN)), not a bare #N or a raw URL. The autogenerator already converts most of these — re-check.
  • If the entry is only Closes #N. with no description, fetch the PR body or the linked issue title and write a one-sentence description of what the user observes, then put Closes [#N](...) at the end.
  • Multiple Closes/Fixes references can stay; put them all at the end.

Examples of entries that should be promoted (from past releases):

  • Fix renames of columns missing in part. → Bug Fix.
  • Write Parquet bloom filters. → New Feature.
  • Reverse key support in PartsSplitter. → Bug Fix (it had been gated as experimental but was shipping).

Examples that should be deleted:

  • update arrow submodule for table reader fixes. (build plumbing)
  • tests: ..., ci: ..., Fix flaky test_*, Update README.md.
  • Sync private., Add a test for [#NNNNN]. (no user-visible change).

4. Prune #### Build/Testing/Packaging Improvement

Most CI infrastructure entries (praktika, internal CI fixes, integration-test plumbing, fast-test tweaks) are removed. Only items that affect external users or distributors stay. Keep:

  • Toolchain/dependency bumps a user might notice (Bump curl to ..., Update to embedded LLVM 19, Restore QPL codec).
  • Build-system changes that affect packagers / contributors building from source (Raise minimum required CMake version to 3.25, Support build HDFS on both ARM and Intel mac, Fixes to allow building with clang20).
  • Docker image behaviour visible to users (Disable network access for user default in docker image.).

Delete:

  • Anything starting CI:, ci:, tests:, Fix flaky , Disable test, Bump pytest, Update version_date.tsv, Switch ... workflow, Praktika ..., Sync ..., Refactor , chcache: (unless it's a user-relevant build issue).
  • Internal coverage / digest / scheduling / artifact-path tweaks.

5. Per-entry text rewrites

For every remaining bullet, in the order below:

5a. Strip leading filler

  • A literal leading ... produced by the autogenerator's bullet cleanup — delete it.
  • TBD. / TODO: ... / WTF is that? — the entry is unfinished. Either rewrite it from the PR title, or delete and tell the user. This applies to author-left TODOs only — a TODO: that the maintainer (or a previous editing pass) prepended as a release-blocking flag stays until its issue is resolved; see §9.
  • What: prefix produced by Cursor/AI bot PRs — delete the prefix.
  • This PR ... / Changes in this PR: 1. ... / In this PR ... — rewrite to start with the user-visible effect.
  • Doing the rewrite in the last major PR ... / first-person developer context — delete or rewrite.
  • Follow up for https://...PR/N. / Follow-up to [#N]. with no other description — delete the entry; it has no user-facing content. If there is real content after the follow-up reference, keep just that.
  • An entry that is a real, user-visible change but is written incomprehensibly (only in terms of internal classes/methods, e.g. naming MergeTreeSink::consume and a delayed_chunk pattern instead of the observable effect) — do not leave it and do not delete it. Open the PR (gh pr view <N> --json title,body), read what it actually does, and write a proper user-facing entry from scratch (keep the original PR/author link). Example: PR #105943's autogenerated entry described delayed_chunk/StorageSnapshot internals; the PR adds the setting wait_for_part_commit_in_dependent_materialized_views, so the correct entry describes that setting and the observable effect (a cascading MV that joins back to its source can now see the row being inserted). Never ship a TODO/FIXME placeholder in its place.

5b. Strip trailing artifacts

  • An entry whose whole text is wrapped in bold, ending with a doubled period — **Reject PromQL timestamps and durations that exceed the Int64 range.**. — strip the ** markers and the extra period. This comes from PR bodies whose changelog entry is written in bold; 26.8 had six of these from one author and they all had to be fixed by hand.
  • ### Documentation entry for user-facing changes and anything after it — the autogenerator sometimes captures this from PR bodies. Cut it.
  • "I'll write more info later." / "TODO: explain better" — cut.
  • A bare PR/issue URL left inline in any phrasing (not just Closes/Fixes) — convert it to the markdown-link form. E.g. Follow up to https://github.com/ClickHouse/ClickHouse/pull/106387.Follow up to [#106387](https://github.com/ClickHouse/ClickHouse/pull/106387).

Do not strip trailing Closes #N / Fixes #N / Closes [#N](...) references. They are valuable. If they're at the start of the entry, move them to the end after the description. If they're a bare URL like Closes: https://github.com/ClickHouse/ClickHouse/issues/N, convert to the markdown-link form Closes [#N](https://...) (the autogenerator already does this for most cases — re-check). See §3 for the full "Closes/Fixes" rule.

5c. Backtick code-like tokens (most frequent edit by far)

Anything you would type into clickhouse-client should be in backticks. Specifically:

  • Functions without parens: geoToH3()geoToH3, ToTimetoTime, extractKeyValuePairs, tokens, countMatches, printf, etc. The project rule (CLAUDE.md): "write names of functions and methods as f instead of f() — we prefer it for mathematical purity." Table functions count too, including in a comma list: file() / s3() / azure() / url()file / s3 / azure / url.
  • Literal keyword/value names that name a feature variant read like ordinary words but are still literals — backtick them: statistic types (basic, countmin, minmax, tdigest), codec names, layout names, mode strings. E.g. "Support basic statistics", not "Support basic statistics".
  • Settings: parallel_inserts, s3_slow_all_threads_after_network_error, geotoh3_lon_lat_input_order, enable_url_encoding, etc.
  • Types: Time, Time64, JSON, Variant, BFloat16, Decimal, LowCardinality, Array, Tuple, Nullable, Map, Float32, Float64, IPv4, IPv6, Date32, DateTime64.
  • Engines / formats / catalogs: MergeTree, ReplicatedMergeTree, Iceberg, DeltaLake, Kafka, Parquet, Arrow, S3Queue, RabbitMQ, Redis, KeeperMap, PostgreSQL, MySQL, Azure. (The autogenerator usually doesn't backtick these.)
  • SQL fragments: SET TIME ZONE 'tz', SET session_timezone, ALTER TABLE ... MOVE|REPLACE PARTITION, RENAME COLUMN, DROP COLUMN, CODEC(ZSTD, DoubleDelta), CREATE TABLE, SELECT ... FROM ....
  • Special tokens: -If combinator, version-hint.txt, _part_offset.
  • Type-conversion and pseudo-code expressions: each conversion pair goes into one backtick span — DateTime64(x) <-> DateTime64(y), Decimal(x) <-> Decimal(y), Float32 <-> Float64 (a 26.8 entry listing these bare had to be fixed in a follow-up commit).
  • Bundled tool names keep their literal lowercase form in backticks: chdig, not "Chdig".

Don't backtick prose nouns (the user, a query, the index) — only literal identifiers and code.

5d. Capitalize proper nouns

icebergIceberg, azureAzure, delta lake / delta-kernelDeltaLake, parquetParquet, kafkaKafka, rustRust, postgresPostgreSQL, mysqlMySQL. (Skip if the word is already inside backticks as a literal config value.)

5e. Type-name compounds

Capitalize compounds like float-to-stringFloat-to-String when used as a noun (e.g. "Faster Float-to-String conversion").

5f. Common typos and small grammar fixes

Observed across releases:

  • Clickhouse / clickHouse / Click HouseClickHouse. The clickhouse_spelling style check accepts only ClickHouse and the token spellings clickhouse and CLICKHOUSE, so a misspelling inherited from a pull request body fails the CI of the changelog pull request itself (it did, on 2026-08-25).

  • looselose

  • PropogatePropagate

  • on flyon the fly

  • FIxFix

  • 2 casestwo cases (spell out small numbers in titles)

  • Falsefalse and Truetrue when they refer to ClickHouse setting values (these are lowercase in SQL).

  • NOT NULL columnnot-Nullable column (use ClickHouse type terminology, not SQL standard terminology).

  • NULL (SQL keyword) stays uppercase.

  • Normalize ISA / SIMD names: avx512AVX-512, avx2AVX2, sse4.2SSE4.2.

  • Inclusive terminology: whitelistallow-list, blacklistdeny-list.

  • Expand a non-obvious abbreviation on first use: DP JOIN reorderingDP (dynamic programming) JOIN reordering.

  • Fix any wrong capitalization of the product name: the only accepted spellings are ClickHouse, clickhouse and CLICKHOUSE. The CI style check clickhouse_spelling greps CHANGELOG.md for the bad variants and fails the build; 26.8 needed a dedicated follow-up commit for a single one. Before finishing, run the same check over the final file:

    grep -niE 'click[ _-]?house' CHANGELOG.md | grep -vE 'ClickHouse|clickhouse|CLICKHOUSE'
    
  • Homophone typos: looselose, and similar.

  • No spaces just inside parentheses: ( introduced in 26.7 )(introduced in 26.7).

  • Prefer commas over a parenthetical em-dash pair in entry text: — including MergeTree tables —, including MergeTree tables,.

  • Drop a redundant Experimental: prefix from an entry that already sits under Experimental Feature.

  • Drop a trailing space before .

  • Replace double spaces.

5g. Translate developer-jargon to user-visible effect

When the entry reads as a low-level commit message, rewrite it as a description of what users observe. Real before → after pairs from past releases:

  • Add __attribute__((always_inline)) to convertDecimalsImpl.Better inlining for some operations with Decimal.
  • Try to speedup QueryTreeHash a bit.Speedup comparisons of query trees during the query analysis a bit.
  • Improve Keeper with rocksdb initial loading.Improve the startup of clickhouse-keeper when it uses rocksdb storage.
  • Removed allocation from the signal handler.Fix potentially unsafe call in signal handler.
  • Fix invalid result buffer size calculation.Fix data corruption with CODEC(ZSTD, DoubleDelta). (replaces vague symptom with the user-visible failure mode.)
  • Drop blocks as early as possible to reduce the memory requirements.Reduce memory usage for some window functions.

Strip internal C++ class/method names that mean nothing to a user; state the effect in plain words instead. Real before → after pairs from Alexey's 26.6 cleanup:

  • ... replacing per-chunk column hashing with an IColumn::computeHashInto kernel that uses hardware CRC32C.... replacing per-chunk column hashing with a kernel that uses hardware CRC32C.
  • Squash source blocks before projection.calculate() during MATERIALIZE PROJECTION ...Squash source blocks before calculating projection during MATERIALIZE PROJECTION ...

Cut, don't just translate: when an entry follows a clear effect statement with an accurate but internal root-cause story, delete the story. In 26.8 Alexey reduced a Performance entry to just Appending to a system log queue no longer deep-copies the whole queue when it grows. — cutting a correct explanation about Poco::Net::SocketAddress lacking a move constructor, nothrow-move-constructibility, and a std::vector reallocation under a mutex. One sentence of user-visible effect beats a paragraph of C++ mechanics, even when the paragraph is right.

This is the judgement-call step. If you can't find the user-visible effect from the entry alone, fetch the PR with gh pr view <N> --json title,body and use the title as a starting point.

5h. Match shipped state, not PR-time state

If a function or setting was renamed between PR merge and release (the actual shipped name differs), update the entry. Real example: 25.2 had stringCompare rewritten to compareSubstrings because the function was renamed before release. If you can't tell, ask.

5i. A Backward Incompatible Change entry must say what breaks

Every bullet under Backward Incompatible Change must let a reader tell whether they are affected: what breaks or behaves differently on upgrade, and what to do about it. An entry that only describes the change — Object-storage disk transactions now use the metadata storage's native transactions by default instead of the previous fake transactions. — doesn't justify its section. First try to derive the incompatibility from the PR (gh pr view <N> --json title,body) and write it into the entry. If the PR doesn't say either, don't guess and don't silently move the entry out of the section — prepend a maintainer TODO flag (see §9): TODO: explain the backward incompatibility: or, addressed at the author, TODO: @<author> How is this a backward incompatible change? The changelog entry does not tell:. Both forms are verbatim 26.8 edits (#112757, #89658).

Also check new names introduced by the entry against naming policy: a setting or option with new in its name (26.8: use_new_storage) is forbidden and will be renamed before release — flag it with TODO: we forbid `new` in names, it will be renamed! and tell the user. (The 26.8 one shipped as use_lsmt_storage; the entry had to be updated when the rename merged.)

6. Category reassignment

For each entry, decide if its current category is right. Common moves:

  • Fix/Fixed/Fixes-shaped entry → Bug Fix — but be conservative. Bug Fix is reserved for user-visible misbehavior in the official stable release build. That excludes:

    • Usability improvements ("better error message", "clearer wording", "log less") — these go to Improvement.
    • Fixes for issues that only manifest in debug builds, sanitizer builds (ASan, MSan, UBSan, TSan), or fuzzer-only crashes — these are not user-visible in release and stay as Improvement (or Build/Testing if internal).
    • Fixes for LOGICAL_ERROR exceptions that only fire in debug assertions and produce no incorrect result in release — Improvement.
    • Race conditions or UB that no user has hit because they only occur under sanitizer instrumentation — Improvement.

    Move to Bug Fix only when the bug would produce wrong results, a crash/exception, data loss, or a hang in a user's release build.

  • Measured speedup / Faster ... / Speedup ... / Reduce memory usage → Performance Improvement even if labelled Improvement. Read this broadly: Alexey moved a large batch of efficiency-flavored Improvement entries into Performance Improvement in 26.6. Triggers include reducing memory reservation/footprint/fragmentation (dedicated arena, freeing earlier), avoiding redundant work (caching, dedup of calculations, fewer marks re-read), avoiding copying (hardlink instead of copy), turning a perf optimization on by default, and background-IO or batching changes. This includes "new X" entries whose entire point is efficiency: in 26.8 the new bucketed schema type for system.metric_log moved from New Feature to Performance Improvement. When in the same wave as a significance sort, do the move and the reorder together.

  • A bundled-tool version bump with substantial user-visible features → New Feature rather than Build/Testing, to highlight it (26.8: the chdig update).

  • New SQL surface (function, table function, system table, syntax) → New Feature even if labelled Improvement.

  • Behind a setting and off by default OR explicitly described as experimental → Experimental Feature even if labelled New Feature.

  • Backward Incompatible Change is sometimes wrong when the author was over-cautious. If the change is purely additive (a new behaviour enabled by a new setting that defaults to old behaviour), move it to New Feature or Improvement.

The preferred category order (from utils/changelog/changelog.py, which wraps tests/ci/changelog.py) is:

  1. Backward Incompatible Change
  2. New Feature
  3. Experimental Feature
  4. Performance Improvement
  5. Improvement
  6. Bug Fix (user-visible misbehavior in an official stable release)
  7. Build/Testing/Packaging Improvement

If you create a category that didn't exist in the input, insert it at the right position. Do not rename Experimental Feature to Experimental Features plural — keep it singular for consistency with newer releases.

7. Merge sibling PRs into one bullet

Only merge entries when they cover the same feature or a group of very similar features. Sharing a library or subsystem is not enough on its own — two different Iceberg fixes covering different code paths stay as two bullets.

Valid reasons to merge:

  • Same feature, multiple PRs: a follow-up that finishes / fixes / promotes the same change. Recognise by Follow-up to #N / continuation of #N in the body, or one PR adding the feature behind a setting and a later PR enabling/promoting it (e.g. experimental → GA, beta → GA).
  • Same library version bump done twice in the cycle: e.g. Update chdig to v26.3.1 and a later Update chdig to v26.4.3 in the same release.
  • A group of very similar features added together: e.g. several related arithmetic-or-null functions added in one wave, or a parallel set of tokenizer functions, where the per-function description would just repeat the same template.
  • Near-duplicate entries whose text differs only in one token: e.g. the executable vs executable_pool UDF ProfileEvents entries (#105010 + #105618) — collapse to one bullet covering both.
  • An experimental feature split across several PRs in the same cycle: e.g. the three use_reader_executor PRs (#106570 + #106968 + #107210), or a base feature PR plus a follow-up that extends it (make_distributed _plan #106020 + per-worker-ports #107885) — one bullet, all links.
  • "Supersedes" / "Follow-up to" pairs: when a later PR supersedes or extends an earlier one in the same release (e.g. table-readonly #100950 superseded by #105109), merge them. When you do, drop the now-redundant Supersedes [#N] / Follow-up to [#N] cross-reference between the two merged PRs — it's noise once both sit in the same bullet.
  • "X; now also works with Y" extensions: a small follow-up that just broadens an earlier change folds in as a trailing clause, e.g. ... under a single prompt. [#104299](...) (...). Now also works with syntax highlighting disabled (\--highlight 0`). #106665 (...).`

Alexey merges noticeably more aggressively than a first pass tends to — when two adjacent bullets describe the same feature/subsystem from the same wave of work, prefer one merged bullet over two.

Scan the whole release section for duplicates, not just neighbours. Because the changelog is generated incrementally, a follow-up often lands weeks later under a different category than the base PR, so within-section scanning misses it. Before finishing, grep the whole release section for repeated setting/function/feature names. All four of these had to be merged by hand in 26.8, each pair spanning categories: the gini aggregate function (#112280 + a second full entry for #114643), use_query_condition_cache_for_top_k (#111492 in Experimental Feature + #114539 in Performance Improvement), lazy materialization for Parquet (#110970 + #114262), and adaptive codec selection (#111834 + #113511). Put the merged bullet in the single right category.

Describe the net shipped state. When the later PR changes what the earlier one did — re-enables a default, renames a setting — the merged entry states the final behaviour. Drop transitional narrative that is no longer true at release time: 26.8 cut "(default disabled) ... as a precaution until the soundness of its cache entries is fully established" once the follow-up had turned the cache back on.

Do not merge:

  • Two PRs that touch the same engine but solve different problems.
  • A new feature and an unrelated bug fix in the same component.
  • Things that happen to share a category but have nothing else in common.

Merged form keeps all PR/author links at the end:

* Update chdig to v26.3.1 (...). [#101092](...) (Azat). Update chdig to v26.4.3 (...). [#103145](...) (Azat).

Or rewritten as a single sentence with both links trailing:

* Improve Iceberg and Spark compatibility: fix path handling; enforce ...; add fallback for ... [#99163](...) (Daniil Ivanik). [#100420](...) (Daniil Ivanik).

If the second PR adds nothing worth describing separately, append just its bare link after the first entry's author link:

* Added the aggregate function `gini`, ... [#112280](...) (Amirreza Akhondi). [#114643](...) (Groene AI).

When in doubt, leave them as separate bullets — over-merging makes attribution confusing.

8. Reorder within sections

The autogenerator sorts bullets by ascending PR number. That's almost right. After all other edits:

  • Promote 1–3 headline entries to the top of each section. Headlines are the ones a user would put on a blog post: a major new feature, a big perf win, a default change.
  • Cluster thematically related entries (Iceberg cluster, Web UI cluster, text-index cluster) so they're adjacent.
  • Leave the long tail in PR-number order.

Don't reorder more than necessary — the diff against the autogenerated version should still be readable.

Full significance sort. The maintainer (and an explicit "sort by significance" request) goes further than promoting a few headliners: he re-sorts the entire section from most to least significant, in significance tiers, with related entries clustered inside each tier — and he does this for every category, including Experimental Feature, not just New Feature. When asked to sort by significance, sort the whole section that way; the "don't reorder more than necessary" caution above applies only to mid-cycle incremental passes, not to an explicit sort request or to the final pass. On the final pass (the release date is being set, or the user says the release is being finalized), do the full significance sort of every category without being asked — in both 26.6 and 26.8 the maintainer had to do it by hand because the skill-assisted passes left the sections in PR-number order. Significance reordering and category moves between Improvement and Performance Improvement often happen together — Alexey moved a large batch of memory/efficiency "Improvement" entries into Performance Improvement while sorting (see §6).

Mechanics: reorder by reading the bullets into a map keyed by PR number and emitting them in the chosen order; assert the set of PR numbers is unchanged so nothing is dropped or duplicated. Keep exactly one blank line before the next #### header (the maintainer will notice a missing one).

9. Editorial commentary and maintainer TODO flags

Distinctive maintainer pattern: a clarification or warning is appended after the closing ). of the auto-formatted [#N](...) (Author)., so it visibly belongs to the editor rather than the PR author.

* Improved storage format of statistics. All statistics are now stored in a single file. [#93414](...) (Anton Popov). If you didn't explicitly enable table statistics, you can ignore this item.
* Added system.histogram_metric_log ... [#103046](...) (Stetsyuk). The table structure is likely to be changed in future releases.

Add this only when:

  • A change is dangerous and users need a "you can ignore this" or "this may cause data loss" note,
  • A feature's interface is likely to change,
  • An LTS / GA / beta status update is worth flagging.

Do not use it to replace the entry — only to comment on it.

Prepended TODO: flags. The second editorial device: when an entry cannot be finished yet, the maintainer prepends TODO: <reason> to it and commits it that way. The flag stays visible in CHANGELOG.md until the underlying issue is resolved (by the component author, or by a later merge), shortly before the release ships. Verbatim 26.8 uses:

  • TODO: explain the backward incompatibility: — the entry doesn't justify its Backward Incompatible Change section (§5i).
  • TODO: @Mikhail Artemenko How is this a backward incompatible change? The changelog entry does not tell: — same, addressed at the PR author by @-mention.
  • TODO: wait for the revert-of-revert. — the entry's PR is currently reverted, and a re-apply is expected before the release (§2).
  • TODO: we forbid `new` in names, it will be renamed! — the entry documents a policy-violating name that will change before release (§5i); the flag was resolved by editing the entry when the rename merged.

Use a prepended TODO: only for problems that a human decision or a future merge must resolve — never as a substitute for research you can do yourself (§5a still applies to entries that are merely badly written). List every flag you add, or find still unresolved, in your final summary to the user; none may survive into the finalized release section.

10. Don't touch

  • Backported in #NNN: ... prefixes (the autogenerator adds these).
  • Entries that are already user-friendly and well-formed — most of Alexey's own PRs come through clean. Don't paraphrase for paraphrasing's sake.
  • The [#NNN](https://github.com/ClickHouse/ClickHouse/pull/NNN) ([Author](https://github.com/login)). link format.

How to use the surveyed past releases

If you need a fresh example for any pattern, the diffs are reproducible:

# 25.2: autogenerated -> cleaned up
git diff 4a220b43f0726f075763001317e1335face260f4 9de7775ca60e2b0361a412e61558872aeff12c08 -- CHANGELOG.md

# 25.3: raw -> changelog
git diff f6d201ad74a905caed7027e1800be035e68ae0cb e3be9c079028faf278cc4ff997675d3015f09a7e -- CHANGELOG.md

# 25.5: autogenerated -> changelog
git diff f17c73bce4a09e67cab299fa4ee97235cfaf3922 fefd0fa7b02c229225de26b31b712b6d543e365c -- CHANGELOG.md

# 25.7: raw unfiltered -> changelog
git diff e7fc5b4eaba229dee5626c5a08a246a89a531bd6 b49397e527eee597db3aa391c53e56654e62e39c -- CHANGELOG.md

# 26.8: skill-assisted result -> after the maintainers' manual pass
# (the diff most of the TODO-flag / dedup / significance-sort rules come from)
git diff 26f037689408e03502e6769d2ee5a03a3e4979ce 09e0e517578433f9cb105ff641e30b3a383939a3 -- CHANGELOG.md

Use these when you need to verify whether a specific entry shape was kept, deleted, or rewritten in the past.

Output format

When the file is ready, give the user:

  1. A one-paragraph summary of what changed: section-level cuts, number of category moves, number of merges, anything you couldn't decide on.
  2. A list of items that need their attention (e.g. presentation/video URL placeholders, entries you couldn't classify, suspected duplicates you chose not to merge).
  3. The path to the edited file.

Do not commit. Do not paste into CHANGELOG.md. The user merges it in manually.

Skills similaires