Skip to content

Header typos, the PyPI page, and a release that can be re-run - #10

Merged
SuperInstance merged 2 commits into
masterfrom
claude/init-1wgqn0
Aug 18, 2026
Merged

Header typos, the PyPI page, and a release that can be re-run#10
SuperInstance merged 2 commits into
masterfrom
claude/init-1wgqn0

Conversation

@SuperInstance

@SuperInstance SuperInstance commented Aug 18, 2026

Copy link
Copy Markdown
Owner

A fresh agent installed 1.2.0 from PyPI and typed the mistakes a beginner makes.
It found two real things, and the release pipeline had a third.

Header typos were swallowed in silence

key: Zz | tempo: banana | time: 3-4

compiled happily — C major, 100 bpm, 4/4 — and said nothing. Not on
compile, not on check, and not on info --verbose, which documents itself as
showing every diagnostic.

Each still falls back to a default, which is right: a typo in a header should not
cost you the piece. Falling back without saying so is the same fault as Xm9
compiling to a silent bar.

warning: tempo 'banana' is not a number; using 100
warning: time '3-4' is not a metre; using 4/4
warning: key 'Zz' is not a key; sounding in C major

The key warning names the sounding key rather than the text, because the
unreadable text is kept for display — a chart would print Zz over music in C,
so "using Zz major" would have been no help.

The parsers do not raise on nonsense, so this could not be done by catching an
exception. theory.KEY_RE and Meter.readable name each rule once and serve
both the parse and the question "will parsing work", rather than a second copy of
each pattern drifting from the first.

It found a real one immediately. The bundled Hungarian Rhapsody carries
time: 2/4 (Lassan) then 4/4 (Friska) — a human annotation the metre field
cannot express, silently becoming 4/4. The file is left alone (changing the metre
would change the music) and the warning now says what was ignored.

The README is the PyPI page, and its links did not work there

pyproject.toml sets readme = "README.md", so that file is the project page.
Forty-four relative links and one relative image resolved on GitHub and 404'd
on PyPI — where a pip install user reads them. The page told people to read a
dozen documents none of which they could reach, and to compile examples/… files
the wheel does not ship.

All absolute now; images through raw.githubusercontent.com, because a blob
URL serves HTML rather than an image. The examples section says where the
examples actually are, and points pip users at the bundled library, which is
shipped.

tests/test_readme_links.py buys back what absolute links lose: every URL into
this repository is mapped to a path and checked to exist, images may not use
blob, and the version the README claims must match the tree — it said
"Version 1.0" while PyPI shipped 1.2.0.

A release that can be re-run

Three releases in a row are tags with no GitHub Release: each publish step
failed, and the release-notes job depends on it. Worse, it was a dead end —
without skip-existing, a release whose files reached PyPI another way can never
be re-run, because the upload aborts on "file already exists".

skip-existing: true fixes that, and docs/releasing.md now records both traps
that cost real time today: tagging the commit before the version bump merged
(which the workflow's tag check caught, correctly), and what skip-existing
guards, so nobody removes it as noise.

Also

CLAUDE.md now records the measured drift between plainsong/mcp/ and the
sibling: 240 lines across seven of eight files. Most is mechanical, but the
DNS-rebinding guard existed here and not in the published sibling
— fixed
separately in plainsong-mcp#2. The ensemble= injection machinery here is
documented as unused, since the sibling never imports this tools.py.

Verification

671 tests, 7 specs, ruff clean, 6339 file(s) checked, 6321 file(s) compile exactly as recorded.

One process note worth keeping: my first attempt at the changelog entry was a
str.replace on ## Unreleased, which no longer exists since 1.2.0 was cut. It
matched nothing and wrote the file unchanged — a silent no-op of exactly the kind
this PR is about. Caught because git status did not list the file. The second
attempt asserts its anchor.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc

claude added 2 commits August 18, 2026 02:54
Three releases in a row -- 1.0.1, 1.1.0 and 1.2.0 -- are tags with no
GitHub Release. Each publish step failed, and the release job depends on
publish, so the notes were never created even though the artifacts are on
PyPI.

The publish step now passes skip-existing. Without it a release whose files
reached PyPI any other way can never be re-run: the upload aborts on "file
already exists" and everything after it is unreachable. With it, a re-run
skips the upload and goes on to create the release, which is what re-running
is for.

docs/releasing.md gains the two traps, both of which cost real time today:

Tag the commit that carries the bump, not whatever master was when you
started. v1.2.0 was created on the commit before the bump merged, so the
tree there still said 1.1.0. The workflow's tag check refused it -- exactly
what that guard is for -- and a local build in the same clone quietly
produced 1.1.0 artifacts that PyPI rejected as duplicates. A one-line
`grep __version__` before tagging catches it, and the recovery is written
out, including that a --depth 1 clone cannot delete and re-push a tag.

And what skip-existing is protecting against, so nobody removes it as
noise.

The workflow was parsed to confirm the key landed where intended rather
than trusting the diff. 667 tests, ruff clean, 2,499 sources checked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
A fresh agent installed 1.2.0 from PyPI and typed the mistakes a beginner
makes. Two findings, both real.

Header typos were swallowed in silence. `tempo: banana` compiled at 100,
`time: 3-4` at 4/4, and `key: Zz` sounded in C while keeping "Zz" for
display, so a chart would print one key over music in another. Nothing said
anything -- not compile, not check, and not `info --verbose`, which
documents itself as showing every diagnostic. Each still falls back to a
default, which is right; falling back without saying so is the same fault as
Xm9 compiling to a silent bar.

The parsers do not raise on nonsense, so this could not be done by catching
an exception. theory.KEY_RE and Meter.readable name each rule once and serve
both the parse and the question "will parsing work", instead of a second
copy of each pattern drifting from the first. The key warning names the
sounding key rather than the text, because "using Zz major" would have been
no help.

It found a real one on the first run over the corpus: the Hungarian Rhapsody
carries `time: 2/4 (Lassan) then 4/4 (Friska)`, a human annotation the metre
field cannot express, silently becoming 4/4. The file is left alone --
changing the metre would change the music -- and the warning now says what
was ignored.

And the README is the PyPI page. pyproject sets readme = "README.md", so
forty-four relative links and one relative image resolved on GitHub and
404'd on PyPI, which is where a pip user reads them: the page told people to
read a dozen documents none of which they could reach, and to compile
examples/ files the wheel does not ship. All absolute now, images through
raw.githubusercontent.com because a blob URL serves HTML rather than an
image, and the examples section says where the examples actually are.

tests/test_readme_links.py buys back what absolute links lose: every URL
into this repository is mapped to a path and checked to exist, images may
not use blob, and the version the README claims must match the tree. It said
"Version 1.0" while PyPI shipped 1.2.0.

One process note. The first attempt at this changelog entry was a
str.replace on "## Unreleased", which no longer exists since 1.2.0 was cut.
It matched nothing and wrote the file unchanged -- a silent no-op of exactly
the kind this commit is about. Caught because git status did not list the
file. The second attempt asserts its anchor.

671 tests, 7 specs, ruff clean, 6,321 files compiling to exactly the music
they did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
@SuperInstance SuperInstance changed the title Make a release re-runnable, and write down the two traps that caught us Header typos, the PyPI page, and a release that can be re-run Aug 18, 2026
@SuperInstance
SuperInstance merged commit 387e940 into master Aug 18, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants