mcpbeat

Edit Changelog

clickhouse/edit-changelog

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.

6k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
49040
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/ClickHouse/ClickHouse --skill edit-changelog

What it tells the agent to use

found in the instruction text
Bash runs shell commands — read the instruction before connecting

The instruction itself

26 sections, as written by the author

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 and

verifying against PR descriptions for 26.4. 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

  • 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.

  • Make the edits described under "Edits" below, in the order listed.
  • After each substantive change, show the user a short summary (one line per

non-trivial edit) — don't dump the whole diff.

  • 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".

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). LTS marker

is added only if the user says it's an LTS release. 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.

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:

  • 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.

  • Search the rest of the in-progress changelog for the matching entry by

PR number, title, or topic.

  • If the original PR is in the same release range and is being

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.

  • 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.

  • 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.

  • 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.

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 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`),

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.

  • 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
  • ### 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.

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 (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.

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-kernel

DeltaLake, 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:

  • 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, blacklist

deny-list.

  • Expand a non-obvious abbreviation on first use: DP JOIN reordering

DP (dynamic programming) JOIN reordering.

  • 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 ...`

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.

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. When in the same wave as a significance sort, do

the move and the reorder together.

  • **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:

  • Backward Incompatible Change
  • New Feature
  • Experimental Feature
  • Performance Improvement
  • Improvement
  • Bug Fix (user-visible misbehavior in an official stable release)
  • 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.

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).

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 the default cleanup pass, not to an explicit sort request.

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).

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.

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.

(Author).` 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

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:

  • A one-paragraph summary of what changed: section-level cuts, number of

category moves, number of merges, anything you couldn't decide on.

  • 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).

  • The path to the edited file.

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

manually.

How to use it

Copy the folder

Take clickhouse/edit-changelog from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.