A test that wrote a script and ran it at once could fail with "Text file busy (os error 26)": a
program another test's thread started in the moment the file was open for writing held that
descriptor until it ran its own program. No process but the shell now has one, and it is gone when
the shell has ended.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A change to WAI adds its changelog entry here, as a file of its own, and does not edit the
head of ../CHANGELOG.md. Two changes' entries are then two files, and
the changelog no longer makes the two changes conflict, unless both pick one name (below).
The name is <yyyy-mm-dd>-<slug>.md: the date the entry is written, then a few words
of its title, lowercase, joined by hyphens (2026-11-04-pin-by-default.md). Two changes
that pick the same name collide as files, which git reports, and one of them renames its
file.
The content is exactly the entry the changelog holds, in its form: a ### title line,
a blank line, and the entry's text (bullets, paragraphs, **Breaking:** lines), with no
other heading outside a code block, and one newline at the end.
A break is acknowledged here as in the changelog: wai breaking reads the lines a
change adds to these files as it reads the lines it adds to CHANGELOG.md, so a
**Breaking:** line naming the finding's id in backticks passes it.
A release runs python3 scripts/changelog-assemble.py --write from the repository's
root: every entry goes to the head of ## Unreleased, newest date first and, within a
date, by file name, and the files are removed. Without --write it prints the section it
would write; --check checks every file.
The check.scripts/check-changelog-fragment.py, in the repository checks, fails a
pull request that changes anything under wai/ and adds or edits no entry file here. A
file goes directly in this directory: one in a subdirectory fails, since the assembler and
wai breaking read only this one. A change with nothing for the changelog to say ends one
of its commit messages with the trailer line Changelog: none, exactly, in the message's
trailer block (its last paragraph, after a blank line, beside any other Key: value
trailers). A release's change, which renders these files and removes them, passes.
The transition. A pull request opened before the check reached main passes as it is,
with an edit of CHANGELOG.md or with no entry at all. One opened since that adds its
entry to the head of CHANGELOG.md's Unreleased section fails, and is told to add the file
here instead. The check reads when the pull request was opened from the event payload. It
reads when the check reached main from the first-parent history of the pull request's
base: the committer time of the first commit there whose tree holds the check, which is
the merge that brought it in. Until the check is on main, every pull request counts as
opened before it.