ci: normalize real CRLF in appcast description#254
Merged
Conversation
The `#"\r\n"#` raw string literal matches the 4 characters \, r, \, n
— not CR+LF. GitHub stores release bodies with CRLF line endings, so the
replacement was a no-op and the CRs reached Parsley untouched.
Parsley's `parts()` calls `Scanner.scanUpToString("\n")` to grab the
H1 title. Foundation's Scanner skips newlines by default
(`charactersToBeSkipped` includes \n), so when the input has CRLF the
scanner walks past every newline to EOF, leaving the body empty.
Only releases whose notes start with `# ` hit the H1 branch, which is
why v0.8.1 was the first to publish an empty
<description><![CDATA[]]></description> to releases.coder.com.
jakehwll
approved these changes
May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why does
releases.coder.com/coder-desktop/mac/appcast.xmlship empty release notes for v0.8.1?Live feed today:
VERSION_DESCRIPTIONwas populated correctly in the workflow run — the full release body was on stdin toupdate-appcast. The empty<description>is produced byupdate-appcastitself.Root cause: two bugs lined up
1. The CRLF "normalization" is a no-op. In
scripts/update-appcast/Sources/main.swift:#"\r\n"#is a Swift raw string literal — it's the four characters\,r,\,n, not CR+LF. GitHub stores release bodies with CRLF line endings (gh release view v0.8.1 --jq .body | cat -Ashows^M$on every line), so this replacement matches nothing and the CRs reach Parsley untouched.2. Parsley + Foundation Scanner + CRLF = empty body.
Parsley.parsedoes:Scanner.charactersToBeSkippeddefaults to.whitespacesAndNewlines, which includes\n. When the target ofscanUpToStringis also in the skip set, Foundation walks past every newline to EOF, socurrentIndexends up at the end of the string andbodyis"".The Scanner quirk only fires when the description starts with
#(the H1 branch). v0.8.1 is the first release whose body begins with# Coder Desktop for macOS — v0.8.1; every prior release used GitHub's auto-generated notes starting with## What's Changed, which never enters the H1 branch — so the broken raw-string replacement was silently cargo for months.The preview entry in the same appcast still has a description because its
VERSION_DESCRIPTIONcame from a push event (head_commit.message), which doesn't start with#.Verification
Built
update-appcast's exact deps (Parsley 0.9.1) in a Swift 5.9 container against a CRLF input starting with# Coder Desktop for macOS — v0.8.1:Same Parsley call, same input — only the raw-string delimiters differ.
Belt-and-suspenders alternative (not taken)
If you want to also defend against an upstream stage that ever encodes real CRLF as the literal escape sequence
\r\n(e.g. a JSON-stringified value that wasn't unescaped), you can chain both:There's no current code path that produces the literal-escape variant, so I left that out — happy to add it if you'd like.