Skip to content

Reading an SDK Changelog Before Upgrading in Production

9 min read · updated August 11, 2026

A changelog between two versions of a busy client library is a few hundred entries, most of them typing fixes and generated-code churn. Four or five of them can change what your production sends. This is how to find those without reading the rest.

Establish the actual range first

The single most common mistake is reading the release notes for the version you are moving to. What you need is every note between the version you are actually running and that one, which is usually more releases than anybody expects, because the version in your manifest is a range and the version in production is whatever the lockfile resolved months ago.

# what is actually installed, not what the manifest permits
pip show openai | grep -i version
npm ls openai --depth=0

# in a container, ask the image rather than the repo
docker run --rm your-image:prod pip freeze | grep -i '^openai'

Do this against the running artefact, not your laptop. A laptop that has not reinstalled since the last lockfile change will happily report a version production stopped using. If you deploy from an image, the image is the source of truth.

Write down both numbers. Everything below is scoped to the range between them, and the range is what you will paste into the repository compare view when the changelog turns out to be too terse.

Four categories, in priority order

Read the range once, sorting every entry into one of four buckets. Do not read for comprehension; read for classification.

1. Changed defaults

The highest-priority category and the least loudly announced, because under semantic versioning a default change is not an API change and can ship in a minor. Look for anything altering a default timeout, a default retry count or backoff, a default base URL, a default header, or a parameter the client now sends that it previously omitted. These are the entries that produce a production graph moving with no code diff, which is the failure the pin exists to make attributable in the first place. Anything in this bucket needs a test before you ship, not just a read.

2. Removed or renamed surface

Methods, classes, module paths, exception types, enum members. These fail loudly, which makes them cheap: your type checker or your test suite finds them, and the work is mechanical. Note them, but do not spend your careful attention here. The classic example is the OpenAI Python client’s 1.x rewrite, which removed the module-level openai.ChatCompletion entry point entirely — an upgrade that cannot possibly reach production undetected, and therefore is not the one to worry about. See the fix page for that class of break.

3. Behaviour under failure

Entries touching retries, backoff, timeouts, connection pooling, streaming teardown or cancellation. These are invisible in every happy path and decide what happens during your next provider incident. A changed retry default is simultaneously a category-1 and a category-3 entry, and if you see one, go and read the code rather than trusting the summary line.

4. Everything else

New models added to a type union, new optional parameters, docstrings, generated-code refreshes, test-only changes. Skim the headings and move on. A new optional parameter you do not pass cannot change what you send.

The grep list

If the changelog is long, do the first pass mechanically. Save the notes for the range to a file and search for the words that mark categories 1 to 3 — publishers are not consistent about formatting, but they are fairly consistent about vocabulary.

grep -inE 'break|breaking|default|remov|renam|deprecat|retry|retries|backoff|timeout|stream|error|exception|required|no longer|now sends|behaviou?r' CHANGELOG-range.md

Two words on that list earn their place for non-obvious reasons. required catches a previously optional parameter becoming mandatory, which fails at runtime on the code path you did not cover. no longer catches the phrasing publishers reach for when removing behaviour without calling it a removal — “the client no longer retries on connection errors” is a category-3 entry written as prose.

Read every hit. Discard the ones that touch surface you do not use. What survives is typically three to six entries out of several hundred, and that is the list your upgrade is actually about.

When the changelog is not enough

For a heavily generated client, the changelog frequently says “update API shapes” and means it. When a summary line is too thin to classify, go to the source: the compare view between your two tags shows the real diff, and for a generated client the interesting files are small and few. Look at the request-construction path and the client constructor defaults; skip the generated model types, which are large and almost always additive.

Two providers publish these openly, and reading the constructor defaults directly takes less time than searching for somebody else’s summary of them — the openai-python repository and the anthropic-sdk-python repository both keep their release notes on the releases page with the diff one click away.

Client libraries for these APIs are largely code-generated, so a version number can jump several minors with no behavioural change at all, and a single-line entry can hide a default change. Neither direction is inferable from the version number, which is why the range gets read rather than estimated.

The procedure

  1. Record the installed version from the production artefact and the target version. Both numbers, written down.
  2. Fetch the release notes for every release in that range, not just the target, and concatenate them into one file.
  3. Run the grep above. Read every hit and classify it into the four buckets. Discard category 4 entirely.
  4. For each category-1 entry, write a test that asserts the request body you actually send. A recorded-HTTP fixture is enough: assert that the serialised body contains the fields you expect and does not contain fields you never set. This is the test that catches the client starting to send something new on your behalf, and it runs without a deploy or an API key. See testing for default parameter changes on an SDK bump.
  5. For each category-3 entry, check the new default against the timeout and retry values your service sets explicitly. Where the SDK default now matters to you, stop relying on it and set the value in code — an explicit value is immune to the next change.
  6. Upgrade in a branch, run the suite, and additionally run a fixed-input comparison across the version boundary: the output-change test exists for exactly this and belongs in the same pull request.
  7. Deploy the upgrade on its own, with no other change in the release, and watch p95 latency, error rate by class, and token usage per request for one full traffic cycle. Those three graphs are where a missed category-1 entry shows up.