Verdaccio Pull Request Workflow
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.
A skill your agent uses when cutting, tagging, or publishing a new Pinchy version — e.g.
$ npx skills add heypinchy/pinchy --skill cut-pinchy-release -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install heypinchy/pinchy cut-pinchy-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/heypinchy/pinchy.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/cut-pinchy-release .claude/skills/cut-pinchy-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 "cut-pinchy-release" agent skill from https://github.com/heypinchy/pinchy/tree/main/.claude/skills/cut-pinchy-release into .claude/skills/cut-pinchy-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cut-pinchy-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/heypinchy/pinchy/tree/main/.claude/skills/cut-pinchy-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 heypinchy/pinchy --skill cut-pinchy-release -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install heypinchy/pinchy cut-pinchy-release --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/heypinchy/pinchy.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/cut-pinchy-release .agents/skills/cut-pinchy-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 "cut-pinchy-release" agent skill from https://github.com/heypinchy/pinchy/tree/main/.claude/skills/cut-pinchy-release into .agents/skills/cut-pinchy-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cut-pinchy-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 heypinchy/pinchy --skill cut-pinchy-release -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install heypinchy/pinchy cut-pinchy-release --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/heypinchy/pinchy.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/cut-pinchy-release .cursor/skills/cut-pinchy-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 "cut-pinchy-release" agent skill from https://github.com/heypinchy/pinchy/tree/main/.claude/skills/cut-pinchy-release into .cursor/skills/cut-pinchy-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cut-pinchy-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/heypinchy/pinchy.git --path .claude/skills/cut-pinchy-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 heypinchy/pinchy --skill cut-pinchy-release -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install heypinchy/pinchy cut-pinchy-release --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/heypinchy/pinchy.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/cut-pinchy-release .gemini/skills/cut-pinchy-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 "cut-pinchy-release" agent skill from https://github.com/heypinchy/pinchy/tree/main/.claude/skills/cut-pinchy-release into .gemini/skills/cut-pinchy-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cut-pinchy-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 heypinchy/pinchy cut-pinchy-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 heypinchy/pinchy --skill cut-pinchy-release -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/heypinchy/pinchy.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/cut-pinchy-release .github/skills/cut-pinchy-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 "cut-pinchy-release" agent skill from https://github.com/heypinchy/pinchy/tree/main/.claude/skills/cut-pinchy-release into .github/skills/cut-pinchy-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cut-pinchy-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 heypinchy/pinchy --skill cut-pinchy-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 heypinchy/pinchy cut-pinchy-release --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/heypinchy/pinchy.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/cut-pinchy-release .opencode/skills/cut-pinchy-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 "cut-pinchy-release" agent skill from https://github.com/heypinchy/pinchy/tree/main/.claude/skills/cut-pinchy-release into .opencode/skills/cut-pinchy-release/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cut-pinchy-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.
cut-pinchy-releaseA skill your agent uses when cutting, tagging, or publishing a new Pinchy version — e.g.
Cut Pinchy Release is an agent skill from heypinchy/pinchy. Use when cutting, tagging, or publishing a new Pinchy version — e.g. "cut v0.6.0", "ship the release", "publish the GitHub release", "tag a new version", "release the app". Anything that ends in a vX.Y.Z tag on the Pinchy repo.
Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It works with GitHub, npm and pnpm. The repository describes itself as: Self-hosted AI agent platform built on OpenClaw. Enterprise-ready, offline-capable, open source. 🦞. The licence is AGPL-3.0.
4 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 5159959. 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:
pnpmgitghdockerFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use pnpm, git, gh and docker, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Cut Pinchy Release loads about 11k tokens when it runs. Until then it costs about 62 tokens; SKILL.md has 5,590 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 noted patterns worth knowing about, such as sudo or a known installer.
and the `echo "PINCHY_VERSION=v<tag>" > .env` line under it — and both have to move. It is also the most-read of theseinstance: bump `PINCHY_VERSION` in its `.env` → `docker compose pull && docker compose up -d && docker image prune -f`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 heypinchy/pinchy at commit 5159959, republished under its AGPL-3.0 licence (© heypinchy). 5,590 words, ~11,344 tokens.
.claude/skills/cut-pinchy-release/SKILL.md (or your agent's skills folder).Pinchy releases are tag-driven. One script does everything from a clean main (or a release/X.Y branch — see "Release branches" below):
git checkout main && git pull --ff-only origin main
pnpm release X.Y.Z --verified=$(git rev-parse HEAD) # a leading "v" is accepted too — the script normalizes itThat is the only state-changing command. It bumps the version, makes a chore: release vX.Y.Z commit, tags, and pushes — and the tag push is what triggers .github/workflows/release.yml to build images and create the GitHub Release.
--verified is required: it is your attestation that you verified this exact commit on staging, and the script aborts unless it equals HEAD (§ "Before you run pnpm release", step 4). Do not paste it from muscle memory — run the preflight and clear its [ ] items first, or you are attesting to something you did not check.
The Iron Rule: cut every release with
pnpm release X.Y.Z --verified=<HEAD sha>. NEVERgh release create. NEVER a manualgit tag+ push.
workflow_dispatch on the Release workflow with the tag input (see CONTRIBUTING.md), and it deliberately does not re-create the GitHub Release.gh release create (the v0.5.5 incident)pnpm release (scripts/release.mjs) bumps the version in three files inside the chore: release vX.Y.Z commit, then pushes the tag:
.env.examplepackage.jsonpackages/web/package.jsonpackages/web/next.config.ts derives NEXT_PUBLIC_PINCHY_VERSION from package.json#version, and /api/version reports that value. Skip the bump and the tag says vX.Y.Z while /api/version still reports the old version. This actually shipped on v0.5.5 — someone used a gh release create shortcut, so the bump never ran: the tag was v0.5.5, but the image baked the stale package.json version into NEXT_PUBLIC_PINCHY_VERSION, so /api/version reported 0.5.4. Recovery was a whole v0.5.6 patch release.
Second reason the script must push the tag: the GitHub Release must NOT exist when the workflow starts. release.yml calls gh release list --limit 1 to find the previous tag, which extract-upgrade-notes.mjs needs to build the "Upgrade notes" section of the release body. Pre-creating the release with gh release create makes that lookup return the wrong (current) tag. Let the script push; the workflow creates the Release.
A gh release create shortcut now fails the workflow before any artifact exists, so a botched release leaves no GHCR image to clean up — but it still burns a CI cycle on your deadline:
scripts/assert-package-version.mjs <tag> runs before the image build; fails if package.json / packages/web/package.json don't match the tag.end-user-install-published job smoke-tests /api/version against the tag on the published image.upgrading.mdx is cumulative: one ## Upgrading from v<prev> to <target> section per release. During development the current section is written with the %%PINCHY_VERSION%% placeholder (heading and body), because the next version number isn't known yet. docs/scripts/inject-version.sh resolves that placeholder to the build-time version — so only the single newest section may carry it; every older section must already be concrete (to vX.Y.Z).
The v0.5.8 release forgot to freeze its section: the heading stayed from v0.5.7 to %%PINCHY_VERSION%% and the body kept literal placeholders. That's a silent time-bomb — the v0.5.8 notes render fine for v0.5.8, then mis-render as the next version's the moment newer docs build. v0.6.0's release prep had to repair it.
Three mechanisms now make this impossible:
pnpm release X.Y.Z calls finalizeUpgradeSection() (scripts/lib/release-logic.mjs): it freezes the current from v<prev> to %%PINCHY_VERSION%% section — heading and body placeholders — to vX.Y.Z, and includes the edited upgrading.mdx in the chore: release vX.Y.Z commit. So the release script now touches four files, not three: .env.example, package.json, packages/web/package.json, and docs/src/content/docs/guides/upgrading.mdx.openNextUpgradeSection() adds a fresh ## Upgrading from vX.Y.Z to %%PINCHY_VERSION%% skeleton (both ### subsections plus the standard-flow bash block) so the next cycle's first upgrade note has somewhere correct to land. It sits after the tag on purpose: the tagged tree is what the docs deploy builds, and inject-version.sh would render a skeleton inside the release commit as an empty "Upgrading from vX.Y.Z to vX.Y.Z" section on the live guide.scripts/lib/upgrading-mdx-freshness.test.mjs (via assertNoStaleUpgradeSections, run in pnpm test:scripts) fails any PR where a released version's section still carries %%PINCHY_VERSION%%, where two sections carry it, or where the placeholder section's from doesn't equal package.json#version. The preamble / "Standard upgrade" display placeholder is out of scope. scripts/lib/upgrading-released-sections.mjs fails any PR that edits an already-released section without an Allow-upgrade-note-edit: #NNN trailer or the allow-upgrade-note-edit label — see AGENTS.md § "A Released Upgrade Section Is Immutable".What this means for you when cutting a release:
## Upgrading from v<prev> to %%PINCHY_VERSION%% section (with %%PINCHY_VERSION%% placeholders is fine and preferred). The script freezes it for you at release time.from v<just-released> section), which is the safety net, not a surprise.### subsections, "the section exists" stopped being the same question as "somebody wrote it" — so assertUpgradeNotesWritten fails the release while the section is still byte-identical to the generated template. Any real edit clears it, including a deliberate None.; leaving the template's own None so far. does not.main is trunk — features keep landing on it, always. A release/X.Y branch is cut per minor version, from a frozen, known-good ref, when the scope for that minor is decided. It hosts vX.Y.0 and every patch after it (vX.Y.1, vX.Y.2, …) — one branch per minor, not one per patch. main keeps flowing and becomes the next minor. pnpm release and pnpm release:preflight accept both main and any release/* branch; anything else is rejected.
main first, then is cherry-picked to release/X.Y. This makes "main never loses a fix" structural rather than a remember-to-back-merge chore. Back-merge (release branch → main) is only for a fix that is genuinely branch-specific — main has already refactored the affected code away and the fix doesn't apply there.:next tag tracks main, so it is the wrong pin for a release-branch candidate. .github/workflows/pre-release.yml builds release/X.Y pushes to a branch-scoped moving tag rc-X.Y (e.g. release/0.9 → rc-0.9) instead of next — this keeps two concurrent release branches from clobbering each other's candidate image. Pin staging to either :sha-<short12> (an exact ref) or :rc-X.Y (latest on the branch), never :next, while verifying a release-branch candidate.pnpm release X.Y.Z --verified=$(git rev-parse HEAD) from release/X.Y — same Iron Rule, same script, just a different branch checked out. The attestation matters more here, not less: :next is the wrong image for a release-branch candidate, so verify on :rc-X.Y and attest to the branch's HEAD. The tag push still triggers release.yml (tag-triggered, branch-agnostic — nothing changes there). A patch release is more upstream-first fixes cherry-picked onto release/X.Y, then pnpm release X.Y.(Z+1) from that branch.upgrading.mdx after release. pnpm release freezes the current section's %%PINCHY_VERSION%% placeholders in the chore: release commit, and opens the next cycle's section in the follow-up commit — both on the release branch. Both must also reach main, or main's upgrade notes keep stale placeholders, lose the frozen section, and have no open section for the next note to land in. Cherry-pick both upgrading.mdx commits (or just those hunks) to main as an explicit post-release step. Caveat while #1028 is open: main's package.json is not bumped by a release-branch cut, so forward-porting the open section alone turns the freshness guard red on main (it requires the placeholder section's from to equal package.json#version). Bump main's version in the same forward-port, or resolve #1028 first.main — forward-port them too. pnpm release bumps README, .env.example, package.json/packages/web/package.json and both marketplace templates only on the branch it runs on. Nothing re-bumps them on main at its "own next cut" — that next cut is itself a release-branch cut, which has the identical blind spot. Left alone, main's pins sit stale for the entire cycle: .env.example and both marketplace templates stayed at v0.8.0 through all of v0.9.0, and PR #1053 could only hand-fix the README pin because the marketplace-version guard checks templates against .env.example, itself stale on main (#1079). See "After the release" → "Forward-port the version pins to main" for the mechanical step; same #1028 caveat as upgrading.mdx applies — bump main to the tag you just cut, not to a placeholder.pnpm releaseStep 0 — run the preflight and turn every [ ] into a blocking task. pnpm release:preflight <version> prints the gate status plus the manual gates the script can't enforce: a release-specific staging checklist auto-derived from this release's upgrade notes (the #### … subheadings under ### Breaking changes / ### Upgrade notes), the standard regression smoke, and the PWA check. This exists because manual gates that live only as prose get silently skipped next to the script's hard gates — that is exactly how v0.6.0 shipped with the staging click-through never done.
So, mechanically:
pnpm release:preflight <version>.[ ] it prints, create a task (TodoWrite/Task), and make the pnpm release task blockedBy all of them. Do not start the release task while any remain open.staging.heypinchy.com), pinned to the candidate image the preflight names — :next when you are cutting from main, :rc-X.Y (or :sha-<short12>) from a release/X.Y branch, see § "Release branches". Staging carries the upgrade path + real agents/data; the ephemeral CI E2E stacks don't. The release-specific items are different every release, which is why they're generated from the notes rather than hardcoded.pnpm release <version> --verified=$(git rev-parse HEAD) command. The --verified SHA ties your attestation to the commit you actually tested on staging, and release.mjs hard-fails without it — a missing, short, or non-HEAD SHA aborts the release (#1085; until then the flag was parsed by nothing and silently discarded, so attesting and not attesting produced identical runs). Like the docs-review hook, it is not a fraud boundary: you can type the SHA without opening staging, exactly as you can git push --no-verify. It makes forgetting impossible, which is the failure mode that actually happens — and it anchors the attestation to one commit, so landing another commit invalidates it.The preflight's staging checklist is auto-derived from the upgrade notes (#### subheadings) — that is a subset, not the coverage plan. Upgrade notes call out breaking changes and what an operator must know; they miss most shipped features (v0.9.0's Knowledge Base, IMAP connect flow, chat slash-commands, odoo_reconcile, auth error-honesty were none of them upgrade-note subheadings). Build the actual plan from the full changelog:
git log v<prev>..main --no-merges --pretty=format:'%s' | grep -iE '^(feat|fix)'Then three moves before you test anything:
Scope out what has no runtime surface — don't test it. Dark foundations (schema/tables/plumbing merged ahead of the feature that will consume them — grep the upgrade notes and commit bodies for "foundation" / "no runtime surface") and internal-only tooling (eval, CI guards, scripts/) are not user-testable. v0.9.0 shipped ~75 such commits (the whole inbox-agent/email-workflows cluster + eval/kb-eval). Listing them as "to test" burns actions hunting for UI that doesn't exist — name them explicitly as out-of-scope so the human doesn't hunt either.
Cluster into minimum-action super-flows. One well-chosen end-to-end flow exercises many features + fixes at once: a KB-agent setup+query covers ingest, pgvector, hybrid retrieval, citations, abstention, offline embedding and a dozen KB fixes in two questions; an email→Odoo booking covers read, attachment, vision, odoo_read/create/reconcile, duplicate-guard and audit in one run. Optimize for max coverage per action, not one test per commit.
Split every cluster into an agent half and a human half — and hand the human theirs explicitly. This is sharper than the test-and-fix loop's "gates only a human can close" item below: it divides every feature, not just the agent-impossible ones.
Deliver the split as a table (cluster → agent verifies X / human checks Y), the human half prioritized, before you start driving your half.
The CONTRIBUTING item reads "clicked through today," but a passive click-through is the weakest form of this gate — it confirms the app boots, not that the release works. The version that actually catches blockers, and is now the standard, is an autonomous test-and-fix loop you run yourself before handing back for the human's "go" (the v0.8.0 email→Odoo pass caught three shippable blockers this way — a false-success invoice read, a missing vision fallback, and a duplicate-booking bug):
pre-release.yml to publish this branch's candidate image (:next from main, :rc-X.Y from release/X.Y), refresh staging (docker compose pull && up -d && docker image prune -f), and confirm the regenerated openclaw.json actually reflects the change. Verifying against a stale image proves nothing — and on a release branch, verifying against :next proves something about main instead.tools.allow fail-closed, permission scoping) and the recovery paths (reconcile-on-reload). "More problems found is better," not "confirm none exist."tool.<name> rows — outcome, detail, error), the OpenClaw container logs (which model actually served a call — e.g. a tool.pdf attempts array showing the primary 401 and the fallback succeeding), the regenerated openclaw.json, and cross-validation of the numbers themselves (line items summing to the stated subtotals). Scout the state deterministically over SSH before driving the browser — inspect config/DB/audit to pick the right agent and predict the outcome, instead of clicking blind.main, then cherry-picked to the release branch if you are cutting from one), redeploy staging from the refreshed candidate image, and re-verify end-to-end. Loop until the flow is clean. "Keine bekannten Bugs in Releases."/reset trips the assistant-ui shrink defect 100% of the time, ChatCrashBoundary swallows it, and the only evidence is two console lines (#944). Nothing in CI catches this either — of 64 E2E specs exactly one subscribes to pageerror, and only for diagnostics (#945). Clear the console, act, read it back; a screenshot that looks right proves nothing about it.integration_connections points straight at the company's real books. The disposable target is odoo-demo.heypinchy.com (Odoo 19 Community, rebuildable in ~10 min); it lives outside this repo, in ~/projects/odoo — reset recipe in that repo's docs/pinchy-demo.md, seed data via its scripts/setup_crabon_demo.py, credentials in the operator's local ~/.config/odoo-demo.env. Expect to ask the user to add the connection; staging carried none as of v0.9.0.allow_duplicate: true exists for: the agent overrides, the bill is created, and the result proves the override works, not the block. Write the adversarial prompt so the only correct behaviour is refusal — then a create means the guard failed. Same for permission tests: state the goal, never the escape hatch.pnpm release. Your job is to make that "go" a rubber-stamp by having already found and fixed everything findable.Work through every item in CONTRIBUTING.md § "Pre-release checklist" — that is the canonical, always-current list, so don't re-derive or copy it. The script and CI already enforce the mechanical gates (clean tree, on main or a release/* branch, CI green, tag free, upgrading.mdx section present with both subsections, pnpm audit --audit-level=high --prod). The human judgment calls the script can't enforce — verify each against CONTRIBUTING — include:
main; pnpm outdated reviewed.Dockerfile.openclaw version bumped if OpenClaw was upgraded.update-ollama-cloud-models skill every release to refresh the catalog.docs/src/content/docs/guides/upgrading.mdx has a new ## Upgrading from v<prev> to %%PINCHY_VERSION%% section containing ### Breaking changes (write "None." if none) and ### Upgrade notes. The script aborts without it, freezes the placeholder for you at release time, and a CI guard rejects stale placeholders — see "The upgrade-notes section auto-finalizes" above.:next / :rc-X.Y) + PWA install check.v<prev>..HEAD release delta — see the section right below.Not instead of per-PR review, and not per PR. It belongs here, at the end, because the two most valuable kinds of finding are ones no per-PR review can produce:
main and simply not be in the branch you are tagging. Four v0.9.0 findings were exactly that. Three of them: mail-host-guard.ts absent from release/0.9 while the IMAP routes it guards were present; and — both in openclaw-config/write.ts — the plaintext guard's on-disk baseline absent, so on an install already carrying a legacy plaintext key the absolute scan rejected the payload of every targeted write, and the terminal .catch on pushConfigInBackground absent, so that rejection became an unhandled rejection in a voided coroutine. Net effect: a Telegram disconnect answers HTTP 200 with outcome: "success" from the route while the config write carrying the removal is dropped — the bot token stays live. Every one had been reviewed and approved on main. The defect is the backport, and only a whole-delta review sees it. This is the sharpest argument for the release-branch model having a gate of its own.pluginConfig (old), allowed_paths was confined in POST only (old), and a new route read that field to serve bytes to the browser (new, this release). Together: any member could read the AES master key. No single PR contains that bug.Mechanically:
/security-review picks its own working directory — in the v0.9.0 pass it silently reviewed an empty diff in an unrelated worktree, and would have reported no findings. State the range and the checkout: v<prev>..HEAD in the release-branch worktree, and sanity-check git diff --stat before believing any verdict, a clean one most of all./^fc00:/ was reported as missing fd00::/8, but ULA is fc00::/7 and that regex anchors a whole hextet — so it matched only literal fc00: and let through nearly all of fc00::/7, fc01:–fdff:, fd00::/8 (the half actually in use) included. The fix is /^f[cd][0-9a-f]{2}:/i, and the same read turned up :: unguarded beside it. Read the pattern, don't accept the summary.fetch never follows a redirect, so three "refuses to follow the redirect" tests passed identically against the vulnerable code. Run every new security test against the unpatched source and watch it fail before you believe it.main — before the cutShort, and not optional. The docs published from this branch are what users read about the version they run, so a correction stranded on main is a wrong page in front of customers for a whole cycle. v0.9.0 shipped exactly one such error and it stood the whole time: llm-providers.mdx on release/0.9 said "Pinchy will not silently re-assign agents" while the shipped DELETE /api/settings/providers migrates them. The corrected sentence had been on main since before the cut.
git diff --stat origin/release/X.Y origin/main -- docs/src/content/docs/Read the diff and put every hunk in one of two buckets:
main has → leave it. Documenting it here would promise users something this release does not contain.The tell is not the size of the hunk, it's what it replaces: an added section is usually a new feature, a changed sentence is usually a fix. Read the changed sentences first.
Don't shortcut this by deploying docs from main. Pinning the docs to the release is what makes them describe the software users actually run.
CI now covers the mechanical half — docs-coverage, docs-consistency and check-docs-required run on every PR, so by the time you are here no API route, audit event or tool is missing from a reference and no page is orphaned. Don't re-do that by hand.
What's left is the reading, and it's the part that found the real damage in the v0.9.0 audit: a documented response body that was fiction, a page contradicting itself 120 lines apart, a table listing 2 of 35 templates. Use the review-docs skill, scoped to the features this release changed.
"Workflow green" is not "content right" — v0.5.8 proved that when the version-placeholder freeze silently didn't happen. Once screenshots.yml has deployed, check on docs.heypinchy.com itself:
%%PINCHY_VERSION%% survives anywhere;Watch BOTH post-tag runs to a verified green — never trust a watch's exit code. The tag push starts the Release workflow (images + GitHub Release) and a fresh CI run on the new chore: release commit. That commit carries new content the pre-release CI never saw — the version bumps and the auto-finalized upgrading.mdx — which is exactly how v0.6.0 turned main red (the finalize removed the %%PINCHY_VERSION%% placeholder a test anchored on). So a green pre-release CI does not mean the release commit is green.
gh run watch <id> and gh pr checks --watch routinely exit 0 prematurely: right after a push no checks are registered yet (zero-checks race), and staged needs: jobs (E2E) only start after the build job. The watch exiting is not proof.gh run view <id> --json status,conclusion → completed / success with no failed jobs; and for a PR, gh pr view <n> --json mergeStateStatus → CLEAN (not UNSTABLE/BLOCKING). Only then merge/announce. A Release-workflow failure means the release is not installable — recovery in CONTRIBUTING.md § "If the release workflow fails".Forward-port the version state to main. pnpm release bumps README's quick-start pins, .env.example, package.json, packages/web/package.json and both marketplace templates (marketplace/digitalocean/template.json, marketplace/caprover/pinchy.yml) only on the branch it runs on — a release cut from release/X.Y never touches main. main is what GitHub visitors and any one-click catalog see, and left alone this drift survives the whole cycle: .env.example and both marketplace templates sat at v0.8.0 through all of v0.9.0, and a hand-fix could only patch the README because the marketplace-version guard checks templates against .env.example, which was itself stale on main (#1079, #1044). Do it as an explicit commit right after tagging — same shape as the upgrading.mdx forward-port above. The pins do not all take the same value, because they answer two different questions:
.env.example, both marketplace templates. These take the tag you just cut. A tag that does not exist is not installable, so they may never carry a development version. The README names the tag in two places — the raw.githubusercontent.com/.../v<tag>/docker-compose.yml URL and the echo "PINCHY_VERSION=v<tag>" > .env line under it — and both have to move. It is also the most-read of these files, the command a visitor runs off the GitHub front page, and the first pass of #1044 missed it entirely while listing exactly this category.packages/web package.json. These take <next>-dev (e.g. 0.10.0-dev), because main is not the release it just cut — staging tracks :next off main, and a build 400 commits past v0.9.1 must not tell you it is v0.9.1.## Upgrading from v<just-released> to %%PINCHY_VERSION%%.Two tripwires if the step gets skipped: scripts/lib/version-identity.test.mjs fails the PR (per-PR, offline, reads upgrading.mdx's newest frozen section), and the weekly docs-freshness.yml cron (scripts/check-main-version-pins.mjs) goes red once main is a full minor behind. See AGENTS.md § "Two Version Numbers, And Which Question Each Answers".
Deploy the release to the demo + production instances. The published images do NOT deploy themselves — staging tracks :next, but demo and production pin ${PINCHY_VERSION} and only move when an operator bumps it and pulls. Skip this and the release reaches no users: production sat on v0.5.8 across several releases for exactly this reason (no plan step → forgotten), so it missed the v0.7.0 cookie-stability and plugin-deps fixes. On each instance: bump PINCHY_VERSION in its .env → docker compose pull && docker compose up -d && docker image prune -f → verify /api/version reports the new tag + a quick smoke. Mind cross-release migrations when skipping versions (e.g. the v0.7.0 cookie one-time relogin; additive DB migrations run on boot). Treat production as a confirm-first, outward action. NB: superseded once auto-deploy on push to main lands (#184).
Red CI: classify transient vs. real before reacting. Is main green for the same check? A crash in Node/pnpm internals during dependency download (e.g. an undici assert(!this.paused)), a 6h runner stall, or a fresh OSV advisory are infrastructure → a rerun is the correct response. A failure in our own test/build logic is real → fix it, don't blind-rerun. (Flaky tests we own get fixed at the root, never papered over with reruns.)
Tags are immutable — never force-update a tag. A broken release is fixed with a patch release, not a re-push.
Re-check deployment overrides. Any long-running deployment that pins a docker-compose.override.yml (to work around upstream bugs not yet fixed) should be reviewed after each release — the upstream fix may have shipped in this release, in which case drop the override.
Update the marketing website. Reflect the release on heypinchy.com. It's a separate, private repo (heypinchy/website) with its own deploy (push to main → S3/CloudFront) and its own release-update checklist, so nothing in this flow touches it automatically — unlike docs.heypinchy.com, which the release workflow deploys. Finalize the release blog post + screenshots of the shipped UI, update the feature grid / affected feature pages, and refresh /vs/* competitor claims, per that repo's CLAUDE.md → "Release update workflow". The canonical checklist item lives in CONTRIBUTING.md § "Pre-release checklist" → Marketing website.
| Thought | Reality |
|---|---|
"I'll just gh release create quickly" | That's the v0.5.5 footgun. CI fails it. Use pnpm release. |
"I'll git tag and push the tag myself" | Skips the version bump → /api/version drifts from the tag. Let the script tag. |
| "I'll pre-create the GitHub Release, then push the tag" | Breaks the PREV-tag lookup for the upgrade notes. Don't. |
| "The version bump is just cosmetic" | /api/version and the public Releases page read it. It IS the shipped version. |
| "Deadline — skip the checklist" | The checklist is the only thing the script can't enforce. |
| "I can release from this worktree/branch" | Releases cut from clean main or a release/X.Y branch only. The script refuses any other branch (including ad-hoc worktree/feature branches). |
"pnpm release went green, so I'm done" | Green ≠ verified. The staging click-through + PWA are manual gates the script can't see. Run release:preflight, make each [ ] a blocking task, verify on the staging pin it names (:next / :rc-X.Y) first. |
| "Staging booted / I clicked through it, so that gate's done" | Booting ≠ working. Run the active test-and-fix loop: drive real flows adversarially, verify against the audit log + OpenClaw logs (not the UI), and TDD-fix every bug you find before the cut. |
| "Per-PR review covered the security side" | It structurally cannot see the two findings that matter most here: a fix that never got backported to the release branch, and a hole that only composes across PRs. Run the whole-delta review before the cut. |
| "The security review came back clean" | On which diff, in which worktree? /security-review chooses its own directory and will happily report a clean empty diff. Confirm the range with git diff --stat before trusting a clean verdict. |
| "The new security test passes" | Passing proves nothing until it has FAILED against the unpatched source. Three "refuses to follow the redirect" tests passed either way, because a mocked fetch never follows one. |
"That fix is on main, so it's in the release" | Only if somebody picked it. Ask git, after a git fetch — a stale remote ref answers about yesterday's branch: git merge-base --is-ancestor <sha> origin/release/X.Y; echo $? → 0 in, 1 missing, anything else means your sha or ref is wrong. --is-ancestor prints nothing, so read the code, and don't let || echo missing swallow a bad sha as "missing". A commit title says what the commit intended, not where it landed. |
| "The watch exited 0, so CI is green" | gh run/pr checks --watch exits early when checks register late (right after a push) or stage in via needs:. Confirm conclusion: success + mergeStateStatus: CLEAN before merging/announcing. |
| "Pre-release CI was green, so the release commit is fine" | The chore: release commit adds the version bumps + the auto-finalized upgrading.mdx. Watch the fresh CI run on that commit too — v0.6.0 turned main red exactly here. |
| "CI is red — rerun it" | Classify first. Infra (Node/pnpm crash, runner stall, fresh OSV) with main green → rerun. Our own test/build → real, fix it. |
| "The endpoint answered 200, so the credential is valid" | Check which endpoint authenticates. Ollama Cloud's /api/tags and /v1/models return 200 for any token — only /v1/chat/completions authenticates. State what the evidence covers, not what it suggests. |
| "It's not in Pinchy's DB, so it doesn't exist" | Pinchy's tables only know what is wired into Pinchy. The demo Odoo, the seed scripts, the local ~/projects repos are all invisible there. Check the world, not one table — and reconcile against recorded memory before contradicting it. |
| "The test suite went red, so something broke" | Was more than one heavy job running? Three concurrent vitest processes produced 65 "failing" files that were pure contention. One heavy job at a time, or the run is not evidence. |
| "The agent said it worked" | Re-read the store. A reconcile claim is true only if amount_residual really fell to 0 and both journal lines carry reconciled = t; the E2E mock zeroes that field itself, which is exactly why the live check exists. |
package.json#version by hand instead of letting pnpm release bump it — that misses .env.example, the commit, and the tag wiring.main or a release/X.Y branch.upgrading.mdx section → script aborts at the upgrade-notes gate.gh release create "to save a step" → recovery costs a whole patch release.© heypinchy, AGPL-3.0. 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/cut-pinchy-release of heypinchy/pinchy.
Open the folder on GitHubat commit 5159959
Cut Pinchy 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 |
|---|---|---|---|---|---|---|
| Cut Pinchy Release this skillheypinchy/pinchy | 182 | — | ~11k | Automated safety check: Notes | AGPL-3.0 | |
| Verdaccio Pull Request Workflowverdaccio/verdaccio | 18k | — | ~1.9k | Automated safety check: Pass | MIT | |
| Ant Design Mobile Releaseant-design/ant-design-mobile | 12k | — | ~497 | Automated safety check: Pass | MIT | |
| ZCF Release AutomationUfoMiao/zcf | 6.1k | — | ~3.4k | Automated safety check: Pass | MIT | |
| OpenGUI Installer for DSHCore-Mate/OpenGUI | 1.8k | — | ~1.1k | Automated safety check: Pass | Custom licence | |
| Linea Dependency MaintenanceConsensys-Incorporated/linea-attestation-registry | 177 | 1 repos | ~3.7k | Automated safety check: Warn | MIT |
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.
ant-design/ant-design-mobile
Prepares an npm release of ant-design-mobile up to the version commit, then tags and publishes the GitHub Release after you run the publish command yourself.
UfoMiao/zcf
Automates a version release with changesets: analyzes code changes, writes a bilingual CHANGELOG, bumps the version and commits through a release branch and pull request.
Core-Mate/OpenGUI
Installs and verifies the latest stable OpenGUI release in a DeepSeek Harness web profile on macOS without disturbing existing plugins or settings.
Consensys-Incorporated/linea-attestation-registry
Safely plan and execute dependency maintenance for JavaScript/TypeScript (npm, pnpm) and GitHub Actions, including npm lockfiles, pnpm workspaces, catalogs, overrides, SHA-pinned action versions…
seasonedcc/remix-forms
Release a new version of the remix-forms npm package. An agent skill from seasonedcc/remix-forms.
heypinchy/pinchy
Answer questions from the organization's indexed documents using knowledgesearch, and cite every claim back to a retrieved passage.
heypinchy/pinchy
Query and summarize data from a connected Odoo instance with the odoo read tools (describe, count, read, aggregate).
heypinchy/pinchy
Use before opening a PR that changes docs/ or a user-visible surface (an API route, the tool registry, an agent template, the audit event catalogue, the settings navigation, plugin tools), and when…
heypinchy/pinchy
A skill your agent uses when bumping general npm/pnpm dependencies across the Pinchy workspace (root, packages/web, packages/plugins/, docs), when the user asks to "update dependencies," "check for…
heypinchy/pinchy
A skill your agent uses when a new Ollama Cloud model is announced or available (e.g.
heypinchy/pinchy
A skill your agent uses when bumping the pinned OpenClaw core version (openclaw npm package), when preparing a Pinchy release, or when the user asks to "update OpenClaw" / "upgrade OpenClaw" / check…
A skill your agent uses when cutting, tagging, or publishing a new Pinchy version — e.g. Cut Pinchy Release is an agent skill from heypinchy/pinchy.g.
Cut Pinchy Release fits situations like: publishing a new Pinchy version — e.g.
Run `npx skills add heypinchy/pinchy --skill cut-pinchy-release -a claude-code`. Or copy the skill folder (.claude/skills/cut-pinchy-release in heypinchy/pinchy) into .claude/skills/cut-pinchy-release in your project. Claude Code loads it when a task matches its description.
Run `npx skills add heypinchy/pinchy --skill cut-pinchy-release -a codex`. Or copy the skill folder (.claude/skills/cut-pinchy-release in heypinchy/pinchy) into .agents/skills/cut-pinchy-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 heypinchy/pinchy --skill cut-pinchy-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/cut-pinchy-release, .gemini/skills/cut-pinchy-release, .github/skills/cut-pinchy-release and .opencode/skills/cut-pinchy-release in your project.
Going by SKILL.md and its folder, Cut Pinchy Release needs the command-line tools its instructions call (pnpm, git, gh and docker).
SKILL.md contains no URLs. Its commands use git, gh and docker, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Cut Pinchy Release is published under the AGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 11k tokens (SKILL.md is roughly 45k 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 Cut Pinchy Release: Verdaccio Pull Request Workflow (verdaccio/verdaccio, 18k stars), Ant Design Mobile Release (ant-design/ant-design-mobile, 12k stars), ZCF Release Automation (UfoMiao/zcf, 6.1k stars) and OpenGUI Installer for DSH (Core-Mate/OpenGUI, 1.8k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
heypinchy (a GitHub organization) maintains it in heypinchy/pinchy, which has 182 GitHub stars. The repository holds 18 skills in this directory. The repository was last updated on September 21, 2026.
Source: heypinchy/pinchy on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.