Write a changelog entry that tells readers what changed and what to do
Turn raw release notes into a changelog entry that separates changes from required user actions and verification steps. A synthetic software update shows how to make a release note useful to someone who did not make the change.
Content checked 2026.09.20Hand-checked example
Show contents
Who this is forThis guide is for teams that need release notes or project changelogs that tell readers both what changed and what they must do next.
What you need
Python 3.12 and a terminal command that starts that version.
A text editor that can save UTF-8 JSON and text files.
A working folder where the script can create a new folder beneath outputs.
Only the Python standard library is required: json and pathlib.
01Separate the change from the reader's action
A changelog entry should answer at least three practical questions: what changed, what the reader needs to do, and how the reader can check that the update worked. A list of implementation details may be accurate but still leave another team member unsure whether any action is required.
This tutorial uses a small synthetic release with three changes. Each change has an area, a factual description, and a required action. A separate verification list tells the reader what to check after updating.
02Create synthetic raw release notes
The following release information is synthetic and was written specifically for this article. Save it as release_notes.json. It describes version 1.4.0 of a fictional CSV export tool.
json
{
"version": "1.4.0",
"date": "2026-09-20",
"changes": [
{
"area": "Export path",
"change": "Default CSV export folder changed from reports/ to outputs/reports/.",
"action": "Update scripts or shortcuts that expect reports/."
},
{
"area": "Config key",
"change": "Configuration key report_dir was renamed to output_dir.",
"action": "Rename report_dir to output_dir before the next run."
},
{
"area": "Validation",
"change": "Empty customer_id values now stop export instead of being written as blank cells.",
"action": "Fix blank customer_id values before rerunning failed exports."
}
],
"checks": [
"Confirm a test export appears under outputs/reports/.",
"Confirm the configuration uses output_dir.",
"Confirm a row with a blank customer_id stops with a validation error."
]
}
There are exactly 3 changes, 3 required actions, and 3 verification checks. Because the example is synthetic, these paths, configuration names, and behaviors are demonstration values rather than facts about a real product.
03Work out the expected changelog entry by hand
The entry should begin with the version and date. The change descriptions should stay factual. Required actions should use direct instructions, and verification should be separate so readers can distinguish configuration work from post-update checks.
text
Version 1.4.0 - 2026-09-20
What changed
- Export path: Default CSV export folder changed from reports/ to outputs/reports/.
- Config key: Configuration key report_dir was renamed to output_dir.
- Validation: Empty customer_id values now stop export instead of being written as blank cells.
What you need to do
- Update scripts or shortcuts that expect reports/.
- Rename report_dir to output_dir before the next run.
- Fix blank customer_id values before rerunning failed exports.
Check after updating
- Confirm a test export appears under outputs/reports/.
- Confirm the configuration uses output_dir.
- Confirm a row with a blank customer_id stops with a validation error.
The expected entry contains one heading line, three section headings, and nine bullet lines: three changes, three actions, and three checks. No action is hidden inside a paragraph describing the change.
04Generate and validate the changelog entry
Save the following script as useful_changelog.py. It validates the synthetic JSON, creates the changelog text, checks the expected section and bullet counts, and writes the result into a new output folder. The original JSON file is read only.
python
import json
from pathlib import Path
SOURCE = Path("release_notes.json")
OUTPUT_DIR = Path("outputs") / "useful_changelog_result"
OUTPUT = OUTPUT_DIR / "CHANGELOG_ENTRY.txt"
def require_text(value, name):
if not isinstance(value, str) or not value.strip():
raise ValueError(f"{name} must be a non-empty string.")
return value.strip()
def main() -> None:
if not SOURCE.is_file():
raise FileNotFoundError(f"Source file not found: {SOURCE}")
if OUTPUT_DIR.exists():
raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")
with SOURCE.open("r", encoding="utf-8") as stream:
data = json.load(stream)
version = require_text(data.get("version"), "version")
date = require_text(data.get("date"), "date")
changes = data.get("changes")
checks = data.get("checks")
if not isinstance(changes, list) or not changes:
raise ValueError("changes must be a non-empty list.")
if not isinstance(checks, list) or not checks:
raise ValueError("checks must be a non-empty list.")
change_lines = []
action_lines = []
for index, item in enumerate(changes, start=1):
if not isinstance(item, dict):
raise ValueError(f"Change {index} must be an object.")
area = require_text(item.get("area"), f"change {index} area")
change = require_text(item.get("change"), f"change {index} description")
action = require_text(item.get("action"), f"change {index} action")
change_lines.append(f"- {area}: {change}")
action_lines.append(f"- {action}")
check_lines = [f"- {require_text(value, 'check')}" for value in checks]
lines = [
f"Version {version} - {date}",
"",
"What changed",
*change_lines,
"",
"What you need to do",
*action_lines,
"",
"Check after updating",
*check_lines,
]
text = "\n".join(lines) + "\n"
if text.count("\nWhat changed\n") != 1:
raise RuntimeError("Missing What changed section.")
if text.count("\nWhat you need to do\n") != 1:
raise RuntimeError("Missing action section.")
if text.count("\nCheck after updating\n") != 1:
raise RuntimeError("Missing verification section.")
bullet_count = sum(line.startswith("- ") for line in lines)
expected_bullets = len(changes) * 2 + len(checks)
if bullet_count != expected_bullets:
raise RuntimeError("Unexpected bullet count.")
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir()
with OUTPUT.open("x", encoding="utf-8") as stream:
stream.write(text)
print(f"Changes: {len(changes)}.")
print(f"Required actions: {len(action_lines)}.")
print(f"Verification checks: {len(checks)}.")
print(f"Total bullets: {bullet_count}.")
print(f"Output: {OUTPUT.as_posix()}")
if __name__ == "__main__":
main()
05Check the expected result
For this synthetic release, the script should report three changes, three required actions, three verification checks, and nine bullet lines. The expected console output below was derived by hand and is not an execution log.
Confirm that each change appears once under What changed.
Confirm that every change has a corresponding instruction under What you need to do.
Confirm that verification steps are separated from required actions.
Confirm that paths and configuration names are copied exactly from the synthetic source notes.
Run the script again without changing OUTPUT_DIR. It should stop with FileExistsError instead of replacing the previous entry.
06Recognize changelog entries that are technically correct but not useful
Weak changelog text
What is missing
Updated export logic
Readers do not know which behavior changed or whether they need to act.
Fixed configuration
The affected key and required replacement are not identified.
Improved validation
The new failure condition and effect on users are unclear.
Various bug fixes
There is no information about impact, scope, or required checks.
Please update accordingly
The action is too vague to execute or verify.
Avoid making the reader reconstruct the change from issue trackers, commits, or chat history. If an update changes a path, key, command, file format, or required input, name the old and new behavior directly.
07Keep the changelog scoped to user-visible consequences
A changelog entry does not replace technical documentation, migration instructions, test evidence, or version control history. Complex changes may need links to those materials, but the changelog should still summarize the consequence and immediate action.
Not every internal refactor needs a changelog entry. If behavior, interfaces, dependencies, required inputs, outputs, configuration, or workflow do not change for the reader, detailed implementation notes may belong elsewhere.
For larger releases, group related changes by area and distinguish required actions from optional recommendations. Preserve older changelog entries instead of rewriting history unless you are correcting a documented error and can record that correction clearly.
Execution and verification record
2026-09-20 · hand-checked example · target: Python 3.12 · standard library: json, pathlib · no execution
Manually derived the expected changelog entry with three named sections.
Calculated the expected total as 9 bullet lines: 3 changes + 3 actions + 3 checks.
Checked that the synthetic old and new export paths are reports/ and outputs/reports/ and that the configuration names are report_dir and output_dir.
Inspected the script for required text validation, section checks, bullet-count validation, output collision protection, and preservation of the source JSON.
Derived the expected console output by hand.
Verification limits
The code was not executed by the author of this response; no changelog file was created.
The version, paths, configuration keys, and validation behavior are synthetic examples and do not describe a real product.
The script validates structure but cannot determine whether a human-written change description is complete or accurate.
The official documentation URLs were provided from known documentation locations but were not checked live.
Define a simple team filename convention, test it on a synthetic folder, and write a review report without renaming or deleting anything. The checker separates structural errors, invalid dates, and unsupported extensions.
Turn project handover notes into a structured checklist that records what must be transferred, who owns it, where it is stored, and what remains unresolved. A small synthetic example shows how to detect missing owners, locations, and open-issue details before handover.
Instead of stopping at another summary of the meeting, separate agreed work from open items. Turn synthetic meeting notes into a list with owners, due dates, deliverables, and evidence of completion, and write an AI request that assists with the same task.
Manage each article as one row and separate drafting, review, approval, and publishing. Covers importing a local CSV, dropdowns, filter views, sharing permissions, and version history, and sets up a flow for manually updating site files after approval.