Code Review
Azure/Azurite
Review Azurite pull requests with service-aware checks for Blob, Queue, and Table behavior, API compatibility, tests, and release notes.
Cut a Malloy Publisher release, and write the RELEASENOTES.md entries a release ships.
$ npx skills add malloydata/publisher --skill publisher-release -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install malloydata/publisher publisher-release --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/publisher-release .claude/skills/publisher-release && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "publisher-release" agent skill from https://github.com/malloydata/publisher/tree/main/.claude/skills/publisher-release into .claude/skills/publisher-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "publisher-release", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/malloydata/publisher/tree/main/.claude/skills/publisher-releaseType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add malloydata/publisher --skill publisher-release -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install malloydata/publisher publisher-release --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/publisher-release .agents/skills/publisher-release && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "publisher-release" agent skill from https://github.com/malloydata/publisher/tree/main/.claude/skills/publisher-release into .agents/skills/publisher-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "publisher-release", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add malloydata/publisher --skill publisher-release -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install malloydata/publisher publisher-release --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/publisher-release .cursor/skills/publisher-release && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "publisher-release" agent skill from https://github.com/malloydata/publisher/tree/main/.claude/skills/publisher-release into .cursor/skills/publisher-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "publisher-release", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/malloydata/publisher.git --path .claude/skills/publisher-release--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add malloydata/publisher --skill publisher-release -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install malloydata/publisher publisher-release --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/publisher-release .gemini/skills/publisher-release && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "publisher-release" agent skill from https://github.com/malloydata/publisher/tree/main/.claude/skills/publisher-release into .gemini/skills/publisher-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "publisher-release", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install malloydata/publisher publisher-releaseInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add malloydata/publisher --skill publisher-release -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/publisher-release .github/skills/publisher-release && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "publisher-release" agent skill from https://github.com/malloydata/publisher/tree/main/.claude/skills/publisher-release into .github/skills/publisher-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "publisher-release", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add malloydata/publisher --skill publisher-release -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install malloydata/publisher publisher-release --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/publisher-release .opencode/skills/publisher-release && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "publisher-release" agent skill from https://github.com/malloydata/publisher/tree/main/.claude/skills/publisher-release into .opencode/skills/publisher-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "publisher-release", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
publisher-releaseCut a Malloy Publisher release, and write the RELEASENOTES.md entries a release ships.
Publisher Release is an agent skill from malloydata/publisher. Cut a Malloy Publisher release, and write the RELEASENOTES.md entries a release ships. Use when asked to release, cut a release, ship a version, or publish Publisher to npm/Docker — and when a change needs a release note, or you are deciding whether it does and what version to stamp on it.
Its SKILL.md is about 7k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development, covering Changelog and release notes. It works with Docker and npm. The repository describes itself as: Publisher is the open-source analytics engine for Malloy. It lets you define data models once — and use them everywhere. The licence is MIT.
6 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit b9a1a19. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
gitnpmghnodepython3curlFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
pypi.orgFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
PYPI_TOKENGITHUB_TOKENFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Publisher Release loads about 7k tokens when it runs. Until then it costs about 77 tokens; SKILL.md has 3,637 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from malloydata/publisher at commit b9a1a19, republished under its MIT licence (© malloydata). 3,637 words, ~6,961 tokens.
.claude/skills/publisher-release/SKILL.md (or your agent's skills folder).<!--
Copyright (c) Credible Data Inc.
SPDX-License-Identifier: MIT
-->
A release is one workflow_dispatch of Release (NPM + Docker)
(.github/workflows/release.yml), followed by one pull request: the
release/v<version> branch the release published from, merged back into
main. That branch already carries the version bump and the stamped release
notes, so you never create a branch or stamp anything by hand. You open the
PR from the branch the workflow pushed.
Read .github/workflows/CONTEXT.md (path from the repo root) before acting. It carries the publishing rules that are not guessable from the
YAML, and it is the authority when this file and it disagree.
RELEASE_NOTES.md is not written at release time. The PR that changes the
behaviour writes the note, in the same PR, as a ## [Unreleased] section —
which is the only way it gets written by someone who knows what changed. The
release then carries it to users on its own: prepare stamps every
[Unreleased] heading with the release's version on the release branch, and
gh-release puts those sections on the release page. Writing the section is
the whole job; merging the release branch back is the one thing the release
cannot do for itself.
gh-release already attaches an auto-generated "What's Changed" list of merged
PRs. That list is sufficient for a routine patch, so a note is not a per-PR
chore. Write one when the PR list would leave a reader unable to act — when a
user or operator has to do something, or would otherwise draw a wrong
conclusion from an unchanged-looking system:
(BREAKING).Skip it for refactors, tests, docs, CI, and internal changes with no observable effect. When unsure, ask whether a reader upgrading blind could be surprised. If not, the PR list covers it.
Write it for the person upgrading: what changed, what breaks, what to do. The existing sections are the house style — prose, not bullets-only, and specific about the failure mode.
Three states, in order:
## [Unreleased] — <what changed> while the change sits on main
unreleased. This is what an authoring PR writes. Always.## [<version>] — <what changed> once a release has shipped it. CI
writes this — prepare stamps it on release/v<version>, and it
reaches main when that branch's PR merges (step 6). You do not stamp by
hand, and you do not guess the number in advance.Once a section is stamped, it is history and does not get rewritten. A follow-up
that changes that behaviour opens a new [Unreleased] section referencing
the shipped version by number, the way the build-failures section names 0.0.245
and 0.0.246 when describing what 0.0.247 changed.
Never write a version number into a note yourself. Release numbers are assigned
at dispatch time and a release can fail. A failed release's stamp is harmless
only because its branch is never merged; the next dispatch cuts a new branch
from main, which still reads [Unreleased].
0.MINOR.PATCH for every published package, while pre-1.0. It is the policy,
not yet the state: skills follows it, sdk/app/server are still on 0.0.x
until 0.2.0 is cut, create-malloy-package versions on its own 0.0.x line, computed at release time, and the Python client
declares 0.1.0 — sharing 0.1.x with skills. Nothing enforces the shape of
the number for any of them — skills and create-malloy-package resolve their next
version from npm latest at release time, and moving either onto a new minor
line is a judgement call made with an explicit -f version=, not something CI
demands.
#@ persist combination, a load-time
failure on syntax that previously passed. It also covers a removed endpoint or
response field, like 0.0.242's /pages → /data-apps rename.^0.0.250 resolves to
>=0.0.250 <0.0.251 — exactly one version — and ~0.0.250 to
>=0.0.250 <0.1.0, a range whose upper half does not exist. So on 0.0.x no
consumer can say "patches yes, breaking no". ^0.2.0 resolves to
>=0.2.0 <0.3.0, which they can. (On any 0.x line ^ and ~ are
identical — both stop at the next minor.)1.x. Publisher is preview, so a major is a deliberate decision made
with an explicit -f version=; there is no major option on the dispatch. If
it ever happens, note @malloy-publisher/sdk@1.0.1 is published (an old
off-line publish latest moved off long ago) while 1.0.0 is free, and app
and server have no 1.x at all — so a 1.x line has to start at 1.0.0 and
skip 1.0.1, or start at 1.0.2.sdk/app/server train has NOT moved yet. Its latest is still
0.0.250 and the policy takes effect at its first minor release, intended to
be 0.2.0 — not 0.1.0, because skills already occupies 0.1.x and two
trains sharing a minor invites reading one for the other. Until that release is
cut, a default dispatch derives 0.0.251, 0.0.252, … and that is correct.
Do not "correct" it with an explicit version. skills is already on
0.1.x and already follows the policy.Read a release note's own claims rather than its heading when deciding. A section
can be titled (BREAKING) while its bullets say "this cannot affect an existing
package", and the reverse happens too. Propose the level and get the
maintainer's call rather than inferring it from the diff alone — a narrow break
may still be judged patch-worthy, and that judgement is theirs.
prepare derives the version from npm's latest, so a routine release needs
no version input at all:
# a patch — the default
gh workflow run release.yml --repo malloydata/publisher --ref main
# a breaking release
gh workflow run release.yml --repo malloydata/publisher --ref main -f bump=minorThe floor is max(npm latest, main declared) by version sort, and it fails
closed: if the registry will not answer, prepare refuses rather than falling
back to the file. An explicit -f version= still wins outright over bump, and
is what you want for a major, for a prerelease, or to name the number exactly.
Two things worth knowing about the derived path:
bump=minor colliding with
an existing 0.3.0 gives 0.3.1, not 0.4.0.bump=minor is computed off npm's current latest, which is not always the
number the policy wants. Off a 0.0.x floor a minor bump lands on 0.1.0 —
the line skills occupies. That is why the pending move to 0.2.0 needs an
explicit version rather than bump=minor.An earlier version of this section said "prepare's default version is not
trustworthy — always pass -f version=". That was true and is no longer:
#1037 changed the floor to max(npm latest, main declared) with fail-closed
behaviour, so the hole it warned about — a default run walking up from main's
stale number, finding a gap below latest, and moving the dist-tag backwards
with nothing failing — is closed. Passing an explicit version is now a choice,
not a precaution.
| Packages | Version | Decided by | Missing bump caught by |
|---|---|---|---|
sdk, app, server | lockstep | release.yml itself, on a release/v<v> branch | n/a — the release sets it |
skills | its own line, package.json carries a fixed 0.0.0-dev placeholder | release time, from npm's own state (scripts/independent-version.mjs); you, for a minor | n/a — nothing is committed ahead of time to forget |
create-malloy-package | its own line, same placeholder | release time, the same way | n/a, same reason |
malloy-publisher-sdk (Python) | its own line | you, by hand, on main | python-sdk.yml PR check — but see below |
Skills and create-malloy-package no longer have a version to forget. Nothing
is bumped ahead of a release: both packages' package.json declare 0.0.0-dev
in the repo, and what actually publishes is decided at release time by
scripts/independent-version.mjs, called from scripts/publish-packages.sh, and
written into the manifest at publish time. So the pre-release hand-audit that
used to live in step 2 below (comparing main's declared version against npm)
no longer applies to either package — there is nothing on main to compare.
The two are decided differently:
latest's
gitHead — a diff of skills/, packages/skills/, bun.lock and the root
package.json, excluding skills/README.md and
packages/skills/src/*.spec.ts. Changed publishes one patch above the
highest published plain version; unchanged skips.latest into every
workspace it scaffolds — a release changes what it ships even when its own
directory did not. The only skip is a re-run of the same release, detected by
its published publisherServer field already matching.The publish version is one patch above the HIGHEST published plain version
(npm view <pkg> versions --json), not latest plus one — a latest dist-tag
rolled back by hand after a bad release must not make this recompute a version
that's already published. latest's gitHead stays the content-diff baseline
above; only the version arithmetic reads the full versions list.
A hand dispatch of either child workflow with no version input publishes one
patch above the highest published version, the same guard the old PR check
used to enforce, now run once at dispatch time. A minor or major bump is a
hand dispatch with -f version=. After a hand-dispatched skills minor,
hand-dispatch the scaffolder too — it depends on skills' version — or wait for
the next release, which republishes the scaffolder anyway.
One caveat: the Python check has nothing to catch until the first publish
lands, because malloy-publisher-sdk is not on PyPI at all and a project-level
404 is its pass. (It can still go red on an unreadable pyproject.toml, a
version that is not major.minor.patch, or a registry that answers neither 200
nor 404.) It starts enforcing for real the moment a version is up there, so treat
the Python version as unenforced only until then.
Its PyPI publish is paused, though. The publish_pkg python-client line at
the end of scripts/publish-packages.sh is commented out, so no release
dispatches python-sdk.yml today. Before restoring that line, read The first
PyPI publish in .github/workflows/CONTEXT.md: PYPI_TOKEN has to be
account-scoped for a first upload (a project-scoped token cannot exist for a
project that does not), the name has to still be free, and 0.1.0 is what
ships — PyPI filenames can never be reused, so move the version before that
run if it is going to move at all. A failure there is cheap: it is dispatched
last, nothing depends on it, and re-running the job skips whatever already
published.
main's packages/sdk/package.json used to lag npm permanently. It no longer
does: merging the release PR (step 6) brings those three files to the version
that shipped, and CI's Release sync check fails while a shipped
release's PR is unmerged. prepare still takes the max of npm and the file for
its floor rather than trusting either. Ask npm when you want to know what is
published.
git fetch --tags origin
npm view @malloy-publisher/server version # the real current version
git log --oneline "v$(npm view @malloy-publisher/server version)..origin/main"That commit list is what this release ships. If it is empty, there is nothing to release.
Nothing is committed ahead of time for either package, so there is nothing to
confirm here: scripts/independent-version.mjs reads npm's own state at release
time and decides publish/skip/version from it, not from what main declares.
Skip straight to Sanity-check the notes below.
If you want to see what the release will decide before dispatching, ask npm the same questions the release does:
npm view @malloy-publisher/skills dist-tags.latest
npm view "@malloy-publisher/skills@$(npm view @malloy-publisher/skills dist-tags.latest)" gitHead
git diff --quiet "<that gitHead>" origin/main -- skills/ packages/skills/ bun.lock package.json \
':!skills/README.md' ':!packages/skills/src/*.spec.ts' && echo unchanged || echo changed
npm view @malloy-publisher/create-malloy-package dist-tags.latest
npm view "@malloy-publisher/create-malloy-package@$(npm view @malloy-publisher/create-malloy-package dist-tags.latest)" publisherServerchanged (or any diff not unchanged) means skills will publish this release;
unchanged means it skips. create-malloy-package publishes regardless, unless
its publisherServer already equals the version this release is about to ship —
that only happens on a re-run.
Also check the Python client. Its PyPI publish is paused (work in progress): the
release does not dispatch python-sdk.yml until the publish_pkg python-client
line at the end of scripts/publish-packages.sh is restored, so this reading does
not change between releases for now. Its version is in pyproject.toml, not a
package.json, and PyPI answers 404 for the whole project until the first upload
lands — so "PyPI: 404" here is the expected reading today and NOT a reason to
skip the comparison next time.
printf 'python-client: PyPI %s, main %s\n' \
"$(curl -sS --max-time 20 https://pypi.org/pypi/malloy-publisher-sdk/json \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["info"]["version"])' \
2>/dev/null || echo 404)" \
"$(python3 -c 'import tomllib; print(tomllib.load(open("packages/python-client/pyproject.toml","rb"))["project"]["version"])')"create-malloy-package pins the server its generated workspaces run
(SERVER_VERSION in packages/create-malloy-package/src/scaffold.ts). That pin
is substituted at publish time from npm's current @malloy-publisher/server
latest, in the runner's working tree only, so the value committed to the repo is
a dev default and there is nothing to bump before a release. Its publish job then
reads the line back, confirms it matches the registry, and confirms that server
still documents --host.
This is what 0.0.250 was about, and it is worth knowing why the old instruction
existed: the pin was hand-maintained and the publish job refused to ship unless it
equalled npm's latest, so a release that forgot it went red 25 minutes in, after
skills had already published. Setting it in the pre-release PR is now wrong
rather than merely unnecessary — the substitution overwrites it, and a committed
value that happens to differ is not a problem to fix.
The derivation is only correct because publish-packages waits for publish-npm.
It did not until 0.0.250, and on needs: prepare alone this job races the server
publish, so the substituted value would be a release behind and every generated
workspace would pin the previous server. If that needs: is ever narrowed back
to prepare alone, this breaks silently rather than loudly.
The needs: is not enough on its own, because npm can take minutes to show a
version after it publishes. In 0.7.0 latest read 0.7.0 seven minutes after
publish-npm finished. So publish-packages.sh also waits, up to 15 minutes, for
the server's latest to read the new version before dispatching the scaffolder.
It then passes that version to the scaffolder as its server_version input,
because another runner can still read the old latest for a few minutes; the
scaffolder waits until its own latest matches and refuses to pin anything else.
If the wait gives up, neither the scaffolder nor python-client was dispatched.
Once latest reads the new version, re-run the publish-packages job (Re-run
failed jobs), not the whole release.
prepare stamps RELEASE_NOTES.md itself and gh-release reads it back, so
there is nothing to paste. Whether the last release merged back is CI's job:
release-sync.yml compares npm's latest with packages/sdk/package.json
on every PR, every push to main, and after every release run. Read its
result rather than repeating the comparison:
gh api repos/malloydata/publisher/commits/main/check-runs \
--jq '.check_runs[] | select(.name == "Release sync") | "\(.conclusion) \(.html_url)"'
# the narrative this release will stamp and ship
git fetch origin
NOTES="$(mktemp)" && git show origin/main:RELEASE_NOTES.md > "$NOTES"
RELEASE_NOTES_FILE="$NOTES" node scripts/release-notes.mjs extract | grep '^## 'If the check failed, do not dispatch. The previous release's PR has not
merged, so main still reads [Unreleased] for sections that release already
shipped, and this release would stamp and publish them again. The check's error
names the version; finish step 6 for it, which is also the only thing that turns
the check green. release.yml does not run this check itself, so nothing stops
a dispatch but you.
What you are checking in the extract is that the sections listed are the ones this release
actually ships. A section describes work merged to main, so anything sitting
there goes out with this release whether or not it was written for it. Nothing
listed is fine and common — the generated PR list carries a routine patch.
If a section is present that should not ship yet, the work behind it is
already on main and the note is telling the truth; the fix is a release, not
an edit.
prepare derives the version from npm's latest and fails closed if the registry
will not answer, so a routine patch needs no input. See How to bump above for
the policy behind the choice.
# a patch
gh workflow run release.yml --repo malloydata/publisher --ref main
# a breaking release
gh workflow run release.yml --repo malloydata/publisher --ref main -f bump=minor
# an exact number: a major, a prerelease, or a line change like 0.0.x -> 0.2.0
gh workflow run release.yml --repo malloydata/publisher --ref main -f version=<next>Do not merge to main while it runs. publish-packages aborts if main
moves under a watched path mid-release. A RELEASE_NOTES.md-only merge is not
watched, but the window is short — just wait. Do not push to
release/v<version> while it runs either: npm-sdk.yml and
docker-image.yml check it out by name, so a push mid-run can publish two
different commits under one version.
A green tick is not evidence. Read the run's job summary, which names each independently-versioned package as published or skipped and why.
npm view @malloy-publisher/server version
npm view @malloy-publisher/skills version
gh release view "v<version>" --repo malloydata/publisherThis is the one step the release cannot finish itself, and as the agent running
this skill you open the PR. The branch already exists and already holds
everything: prepare committed the three packages/{sdk,app,server}/package.json
versions and the stamped RELEASE_NOTES.md headings to release/v<version>
as one commit. Do not create another branch and do not stamp anything by hand.
Open it once the run has finished, for any release whose sdk reached npm
(npm view @malloy-publisher/sdk version reads it). That includes one whose
gh-release then failed: it shipped, and release-sync.yml stays red until
this PR merges. A release that never reached npm has nothing to merge back:
leave its branch alone (the next dispatch skips past it) and dispatch again.
A prerelease or a +build version never gets a PR.
First confirm the branch is there and holds only the release commit, against
origin/main:
V=<version>
git fetch origin
# 1. prepare pushed it
git ls-remote --exit-code --heads origin "release/v$V"
# 2. one commit ahead of main, titled chore(release): $V
git log --oneline "origin/main..origin/release/v$V"
# 3. only the three manifests and RELEASE_NOTES.md
git diff --stat "origin/main...origin/release/v$V"
# 4. the [$V] sections it stamped, which should match the release page
NOTES="$(mktemp)" && git show "origin/release/v$V:RELEASE_NOTES.md" > "$NOTES"
RELEASE_NOTES_FILE="$NOTES" node scripts/release-notes.mjs extract "$V" | grep '^## '
# 5. no PR for it yet
gh pr list --repo malloydata/publisher --head "release/v$V" --state allRead them in order, and stop at the first surprise:
prepare failed before pushing, so nothing published. Read
the run; there is nothing to merge back.gh release view "v$V" --repo malloydata/publisher.Then open it against main:
gh pr create --repo malloydata/publisher --base main --head "release/v$V" \
--title "chore(release): $V" \
--body "Merges the v$V release branch back into main: sets sdk, app and server to $V and stamps the RELEASE_NOTES.md sections v$V shipped.
Release: https://github.com/malloydata/publisher/releases/tag/v$V"The title matters: the repo squash-merges, so it is what main's history keeps.
The workflow does not open this PR itself because a PR opened with
GITHUB_TOKEN triggers no workflows: main's required checks would never run
and only an admin could merge it. Opened on the user's credentials, the checks
run and any maintainer can merge.
main requires the branch to be up to date, and RELEASE_NOTES.md usually
conflicts. Any PR merged since the release was cut makes the branch stale. A new
[Unreleased] section lands directly above the first stamped heading, which git
treats as one conflicting hunk. Merge main in locally, with a sign-off because
the DCO check is required:
git fetch origin
git switch -c "release/v$V" "origin/release/v$V"
git merge --signoff origin/main
# Only if the merge stopped on a RELEASE_NOTES.md conflict: keep BOTH sides,
# main's new [Unreleased] sections exactly as they are and this branch's [$V]
# headings, then conclude it. The package.json version lines never conflict;
# only release PRs change them.
# git add RELEASE_NOTES.md && git commit -s --no-edit
node scripts/release-notes.mjs extract "$V" | grep '^## ' # exactly v$V's sections
node scripts/release-notes.mjs extract | grep '^## ' # only what merged since
git push origin "release/v$V"The two extract lines are the check that the resolution is right: the first
must list the same sections as the release page, the second only sections
merged after the release was cut. Pushing to the release branch is safe once the
run has finished, since the tag pins the published commit.
Ask the user to merge the PR, or merge it if they asked you to. Merging deletes
release/v$V (the repo deletes head branches on merge); the tag keeps the
commit. Then confirm:
git fetch origin main
git show origin/main:packages/sdk/package.json | grep '"version"' # $V
gh release view "v$V" --repo malloydata/publisher --json body -q .body | head -40If the release page is missing narrative that [$V] sections on the branch
carry, gh-release logged a could not read RELEASE_NOTES.md warning. Add the
output of extract "$V" to the page by hand with gh release edit.
Left unmerged, release-sync.yml fails on main and on every PR, naming
this version; merging this PR is the only fix. Confirm it went green on main
after the merge (the first command in step 3).
Only publish-packages is red → the sdk/app/server release completed and
the tag exists; that job sits outside gh-release's needs. Use Re-run
failed jobs, which re-enters that job alone and skips whatever already landed.
Do not re-run the release — it walks the version forward and burns three
npm versions.
Dispatching the children by hand also works, but it is not the same thing and
the skip does not come with it. Only publish-packages asks the registry and
skips; each child carries a Verify this version is not already published
step that exit 1s on a version npm already holds. So dispatching a child that
already published fails the run rather than no-opping. Ask npm first and
dispatch only the package still missing, keeping skills before the scaffolder:
npm view @malloy-publisher/skills version
npm view @malloy-publisher/create-malloy-package version"main moved during this release" → expected and retryable, same recovery. It can fire on the second package after the first already published, so read the job summary rather than assuming nothing shipped.
Anything after npm-sdk.yml published → not resumable at the same version.
npm versions are immutable; move forward.
Any hyphen in the version skips gh-release and both independently-versioned
packages, because their own versions carry no hyphen and would take over the
latest tag. Ship those from an ordinary release.
A prerelease is also never stamped and never merged back. main's
[Unreleased] sections stay as they are and ship with the next ordinary
release, and extract <prerelease> finds nothing, because no heading carries a
prerelease number.
© malloydata, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/publisher-release of malloydata/publisher.
Open the folder on GitHubat commit b9a1a19
Publisher Release next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Publisher Release this skillmalloydata/publisher | 116 | — | ~7k | Automated safety check: Pass | MIT | |
| Code ReviewAzure/Azurite | 2.3k | — | ~734 | Automated safety check: Pass | MIT | |
| Project Docs Maintainerswimmwatch/cloakbrowser-mcp | 164 | — | ~569 | Automated safety check: Pass | MIT | |
| Project Releaseswimmwatch/cloakbrowser-mcp | 164 | — | ~1.9k | Automated safety check: Pass | MIT | |
| Cutting A ReleaseTriliumNext/Trilium | 38k | — | ~3.2k | Automated safety check: Pass | AGPL-3.0 | |
| Verdaccio Pull Request Workflowverdaccio/verdaccio | 18k | — | ~1.9k | Automated safety check: Pass | MIT |
Azure/Azurite
Review Azurite pull requests with service-aware checks for Blob, Queue, and Table behavior, API compatibility, tests, and release notes.
swimmwatch/cloakbrowser-mcp
Maintain, organize, consolidate, or audit the cloakbrowser-mcp documentation set only when the user explicitly requests project documentation maintenance or an authorized public change requires it.
swimmwatch/cloakbrowser-mcp
Prepare, publish, verify, or recover a cloakbrowser-mcp release only when the user explicitly requests release work.
TriliumNext/Trilium
A skill your agent uses when cutting, preparing, or debugging a Trilium release — bumping the monorepo version, tagging, or diagnosing a failed "Release" workflow run.
verdaccio/verdaccio
Takes a change through a verdaccio pull request: branch, local checks, changeset, title and body, labels, CI and review rounds, and ports to other release lines.
modem-dev/hunk
Maintainer workflow for preparing, publishing, verifying and curating Hunk releases, with confirmation gates before tags, publishes and public edits.
malloydata/publisher
Score one analytical answer against a verified golden, and score which of the entities the golden depends on retrieval delivered to the answerer.
malloydata/publisher
Fix a CRITICAL Trivy finding that is failing CI in this repo (a vulnerability, misconfiguration, or secret from security-scan.yml or image-scan.yml), or add, review, or retire an entry in…
malloydata/publisher
Turn a list of questions into an eval set, whatever shape it arrived in: a JSONL a customer sent, a CSV, a spreadsheet export, a markdown doc, an email thread, or a pull from production logs.
malloydata/publisher
Conduct a local Publisher evaluation loop in five steps: scrape/run, eval, diagnose, improve, checkpoint.
malloydata/publisher
Make the smallest safe Malloy model edit that closes a diagnosed model-owned gap, with a probe receipt for every factual claim.
malloydata/publisher
Decide whether ONE answer matches its golden, and say whether you believe the golden.
Categories
Cut a Malloy Publisher release, and write the RELEASENOTES.md entries a release ships. Publisher Release is an agent skill from malloydata/publisher.md entries a release ships.
Publisher Release fits situations like: asked to release; publish Publisher to npm/Docker — and when a change needs a release note; you are deciding whether it does and what version to stamp on it.
Run `npx skills add malloydata/publisher --skill publisher-release -a claude-code`. Or copy the skill folder (.claude/skills/publisher-release in malloydata/publisher) into .claude/skills/publisher-release in your project. Claude Code loads it when a task matches its description.
Run `npx skills add malloydata/publisher --skill publisher-release -a codex`. Or copy the skill folder (.claude/skills/publisher-release in malloydata/publisher) into .agents/skills/publisher-release in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add malloydata/publisher --skill publisher-release -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/publisher-release, .gemini/skills/publisher-release, .github/skills/publisher-release and .opencode/skills/publisher-release in your project.
Going by SKILL.md and its folder, Publisher Release needs the command-line tools its instructions call (git, npm, gh, node, python3 and curl) and credentials named PYPI_TOKEN and GITHUB_TOKEN. Our summary lists: Python 3; Docker.
SKILL.md names 1 domain. In commands or code: pypi.org; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Publisher Release is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7k tokens (SKILL.md is roughly 28k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Publisher Release: Code Review (Azure/Azurite, 2.3k stars), Project Docs Maintainer (swimmwatch/cloakbrowser-mcp, 164 stars), Project Release (swimmwatch/cloakbrowser-mcp, 164 stars) and Cutting A Release (TriliumNext/Trilium, 38k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
malloydata (a GitHub organization) maintains it in malloydata/publisher, which has 116 GitHub stars. The repository holds 29 skills in this directory. The repository was last updated on October 9, 2026.
Source: malloydata/publisher on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.