Set the Docker Hub short description from the README intro - #32
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #32 +/- ##
========================================
Coverage 44.96% 44.96%
========================================
Files 25 25
Lines 3398 3398
Branches 259 259
========================================
Hits 1528 1528
Misses 1824 1824
Partials 46 46 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Updates the Docker image publishing workflow so Docker Hub’s short description is set automatically from the repository README intro line, preventing drift between the README and Docker Hub metadata.
Changes:
- Adds a workflow step that extracts the README intro line (with CRLF handling) and exposes it as a step output.
- Adds guards to fail the publish if the extracted line is empty or exceeds Docker Hub’s 100-character limit.
- Passes the extracted intro to
peter-evans/dockerhub-descriptionvia theshort-descriptioninput on main publishes.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.github/workflows/build-docker-task.yml:113
- The markdown-structure guard can be bypassed if the first non-empty line after the H1 starts with leading whitespace (e.g.
" ## Heading"or" - item"). CommonMark allows up to 3 leading spaces for ATX headings and list markers, so the workflow could still publish README structure as the Docker Hub short description instead of failing loudly as intended. Trimming leading/trailing whitespace at extraction time makes the subsequent structure/link/length checks reliable.
INTRO=$(tr -d '\r' < README.md | awk 'NR>1 && NF {print; exit}')
|
The collapsed finding on the review of CommonMark does allow up to three leading spaces before a heading or list marker, so the Trimming in the Exercised the bypasses directly rather than reasoning about them:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.github/workflows/build-docker-task.yml:131
- The “markdown structure” guard doesn’t reject some common non-intro markdown constructs. In particular, an image/badge line (starts with
!) or a tilde code fence (~~~) would currently pass thecasecheck and could be published as the Docker Hub short description if the intro line is moved/removed. Also consider rejecting ordered list items (e.g.1./1)) for the same reason.
case "$INTRO" in
'#'* | '>'* | '-'* | '*'* | '+'* | '<'* | '|'* | '`'* | '['*)
echo "::error::Line after the README H1 is markdown structure, not the intro sentence: '$INTRO'"
exit 1
;;
esac
|
Fixed in d6889044cb19c9025f2597376b90c0bb7ed10ddf. All three accepted: The ordered list is a regex rather than a case glob, since a glob cannot express "one or more digits" without also swallowing an ordinary sentence that opens with a number. Verified in both directions, which matters more than the reject cases:
Writing that test caught a real bug. Inline after That is the third round on this step and each one found something real, so thanks. I am stopping the loop here rather than continuing to enumerate markdown constructs: the remaining space is unbounded, the guard now covers every block-opening character CommonMark defines plus links and the length cap, and the failure it exists to prevent (a moved intro silently publishing the next line) is closed. If a construct does slip through later, the step fails loudly on the length or link check or publishes something obviously wrong, rather than failing silently. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.github/workflows/build-docker-task.yml:150
- The Docker Hub short description limit is enforced in bytes, but the guard uses
${#INTRO}(character count under UTF-8 locales). This can allow multi-byte Unicode intros (e.g., an en-dash or emoji) to pass the check while still exceeding Docker Hub’s 100-byte cap, leading to truncation or API failure despite the “fail loud” intent.
if (( ${#INTRO} > 100 )); then
echo "::error::README intro is ${#INTRO} characters; Docker Hub caps the short description at 100."
exit 1
fi
* Set the Docker Hub short description from the README intro The audit's description finding had no mechanism behind it. The docker task pushed the Docker Hub overview from Docker/README.md but never set the short description, so nothing maintained the tagline and it kept whatever was typed into the web UI once. That is why it drifted from the README and why the audit could flag it but no workflow could fix it. The step now reads the intro from README.md rather than restating it, since a literal here would be one more surface to drift, and passes it to the action's short-description input. Two guards fail the publish loudly instead of pushing a blank or truncated tagline, because a restructured README that moves the intro would otherwise silently blank the Docker Hub description: an empty result is an error, and so is one over Docker Hub's 100-character cap. The CR strip matters because README.md is CRLF and a bare CR makes awk read the blank line after the H1 as a non-empty line. Verified: actionlint (which bundles shellcheck) exits 0, and the script was exercised over four inputs - a good CRLF file, a missing intro, a 120-character intro, and an LF-only file - confirming both guards fire rather than assuming they would. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Reject markdown structure and links in the extracted intro Copilot caught that the empty guard covered the wrong failure. Deleting an empty README is not the realistic case; moving or removing the intro line is, and then awk picks up the next construct. Here that is the '## Build and Distribution' heading, which is non-empty and under the cap, so both existing guards pass and a heading ships as the Docker Hub tagline. That is precisely the silent-narrowing shape this step claimed to prevent, so the claim was wrong rather than the code merely thin. Now a line opening a markdown block is rejected, covering headings, lists, quotes, tables, fences, HTML, and link labels. A line carrying an inline or reference link is rejected too, since readme-structure.md requires the intro to be link-free and neither Docker Hub nor the About panel renders markdown, so a link would ship as raw source. Exercised over ten inputs rather than reasoning about them: CRLF and LF happy paths, an absent intro, a heading, a list, an HTML comment, an inline link, a reference link, a 120-character line, and the repo's real README. Every guard fires on its own case and only on its own case. Fixed a bug in the link check while testing it: the pattern was anchored to the end of the string, so it would only have caught a link at the very end of the line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Trim the extracted intro so the structure guard cannot be bypassed Copilot caught that the structure guard read an untrimmed line while CommonMark allows up to three leading spaces before a heading or list marker, so an indented '## ...' would pass the case check and ship as the Docker Hub tagline. Trailing space would pad the published string. Trimming in the awk extraction rather than at each check means every guard downstream reads the same normalized value, so the next check added inherits it instead of having to remember. Exercised the bypasses directly: a three-space heading, a two-space bullet, and a tab-indented heading now all fail on structure, a padded sentence is published trimmed, and a whitespace-only line is skipped in favour of the real intro below it. actionlint exits 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Reject badges, tilde fences, and ordered lists in the intro Copilot's third pass on this guard: a bare image or badge line starts with '!', a tilde fence with '~', and neither was in the case pattern, so either would have shipped as the tagline. Both added, along with an ordered-list check. The ordered list is a regex rather than a case glob, since a glob cannot express "one or more digits" without also swallowing an ordinary sentence that opens with a number. Verified both directions: "1. First item" and "12) Twelfth item" fail, while "2024 was the year it shipped." passes. Testing caught a real bug in that regex. Written inline after =~, bash fails to parse the ')' inside the bracket expression and the step would have died with a syntax error at publish time. actionlint did not flag it. The pattern is held in a variable, which is the form that parses, and the run block now also survives a bash syntax-only parse check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This reverts commit 90504d38e274dad1dd1894dee7fe47d1881ae91f (#32). Nothing is wrong with the implementation. It is the wrong place for it. Setting the Docker Hub short description from the README intro affects every docker project in the fleet, and the hub already owns the audit half: spec/audit.py flags a short description that has drifted from the README intro. Adding the push half in one repo leaves that repo diverged from every other docker project and pre-empts a decision that is not the repo's to make. The hub is working out a deterministic approach. This reverts to the audit-only state so the repo matches the fleet while that happens, and so a later develop -> main promotion cannot carry the mechanism silently. The workflow is now byte-identical to its state before #32, and the implementation with its ten test cases is preserved in #32 for whoever picks the decision up. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The last finding from the post-merge re-audit of
develop:Why it drifted
There was no mechanism behind it.
build-docker-task.ymlpushed the Docker Hub overview fromDocker/README.md, but never set the short description, so the tagline kept whatever was typed into the web UI once and no workflow could correct it. The audit could flag the drift indefinitely and nothing would ever fix it.The fix
The step reads the intro from
README.mdrather than restating it. A literal in the workflow would be one more surface for the same sentence to drift on, and there are already six.Two guards fail the publish loudly rather than pushing a blank or truncated tagline, because a restructured README that moves the intro line would otherwise silently blank the Docker Hub description on the next publish. That is the "gates fail loud, never narrow quietly" rule in
GOVERNANCE.md"Verification Discipline": an empty result errors, and so does one over Docker Hub's 100-character cap.The
tr -d '\r'matters and is not cosmetic:README.mdis CRLF, and a line holding only a carriage return is not empty toawk, so without the strip the extraction returns the blank line after the H1 instead of the intro.Verification
Exercised the script over four inputs rather than assuming the guards work:
intro=Good short line., exit 0::error::No intro line found, exit 1::error::intro is 120 chars, cap is 100, exit 1intro=LF-only file works too., exit 0actionlint(rhysd/actionlint, which bundles shellcheck forrun:blocks) exits 0, andeditorconfig-checkerexits 0.The step is gated
inputs.push && inputs.branch == 'main', so the live Docker Hub value updates on the nextmainpublish rather than on merge.Follows the conformance sweep (
audit run 2026-08-01T16:49:11Z | hub 82b9d5d,develop@95af4d7). The GitHub About description is already set and matches.