You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/09/24 12:22:23 UTC
(skywalking-ai-sessionizer) 01/02: Release skills: what 0.5.0 taught
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch release-skills-after-0.5.0
in repository https://gitbox.apache.org/repos/asf/skywalking-ai-sessionizer.git
commit 3deb8fe72c4befc0199de706d506855fdf681036
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Thu Sep 24 20:15:29 2026 +0800
Release skills: what 0.5.0 taught
The pypi skill now says what to set up before an upload. The upload
that creates a project needs a token scoped to the whole account,
because a project token can only be made once the project exists.
Later uploads use a project token. The token is read from
SW_PYPI_TOKEN, which must be in the environment Claude Code starts
with. The skill downloads from downloads.apache.org first, because
archive.apache.org still had no 0.5.0 fifteen minutes after the move.
It verifies with gpgv, which leaves the user's keyring alone. It then
checks that PyPI holds the files it built.
The homebrew skill ran git rm on a path inside the temporary tap,
outside the repository, where git stops with an error. Its template
fits 0.5.0 and later only, so it no longer claims to fit 0.4.0.
The apt skill tests every older version in the index, because adding a
version moves the one that was newest to the archive.
---
.claude/skills/apt/SKILL.md | 8 +-
.claude/skills/homebrew/SKILL.md | 26 +++---
.claude/skills/pypi/SKILL.md | 180 ++++++++++++++++++++++++++-------------
docs/en/changes/changes.md | 14 +++
docs/en/guides/how-to-release.md | 31 ++++---
5 files changed, 167 insertions(+), 92 deletions(-)
diff --git a/.claude/skills/apt/SKILL.md b/.claude/skills/apt/SKILL.md
index 0fe5e48..d3f4baa 100644
--- a/.claude/skills/apt/SKILL.md
+++ b/.claude/skills/apt/SKILL.md
@@ -166,17 +166,19 @@ refuse the whole repository: stop and tell the user.
## 6. Install through the redirects
Apache httpd serves the directory with its `.htaccess`, as the website does. apt in Debian and in
-Ubuntu then installs the newest version and each older version added, following the redirects to
-the real Apache sites.
+Ubuntu then installs the newest version and every older version in the index, following the
+redirects to the real Apache sites. Not only the versions added: adding a newer version moves the
+one that was newest from the mirrors to the archive, so its redirect changed too.
```sh
docker run --rm httpd:2.4 cat /usr/local/apache2/conf/httpd.conf > "$W/httpd.conf"
perl -0pi -e 's/^#(LoadModule rewrite_module )/$1/m; s/(<Directory "\/usr\/local\/apache2\/htdocs">.*?)AllowOverride None/$1AllowOverride All/s' "$W/httpd.conf"
+older=$(sed -n 's/^Version: //p' "$APT"/dists/stable/main/binary-*/Packages | sort -uV | grep -vxF "$newest" | tr '\n' ' ')
docker network create asz-apt
docker run -d --name asz-apt-site --network asz-apt -v "$APT:/usr/local/apache2/htdocs/apt:ro" \
-v "$W/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro" httpd:2.4
for image in debian:stable ubuntu:24.04; do
- docker run --rm --network asz-apt -v "$W/keys.gpg:/keys.gpg:ro" -e NEWEST="$newest" -e OLDER="<the added versions other than the newest>" "$image" bash -euc '
+ docker run --rm --network asz-apt -v "$W/keys.gpg:/keys.gpg:ro" -e NEWEST="$newest" -e OLDER="$older" "$image" bash -euc '
# The redirects lead to https, and these images carry no certificates.
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq && apt-get install -y -qq ca-certificates > /dev/null
diff --git a/.claude/skills/homebrew/SKILL.md b/.claude/skills/homebrew/SKILL.md
index 7b6f625..a47e5fb 100644
--- a/.claude/skills/homebrew/SKILL.md
+++ b/.claude/skills/homebrew/SKILL.md
@@ -11,8 +11,7 @@ user-invocable: true
From 0.5.0 one formula installs both binaries. Before it there were two, fetching the identical
archive and the identical checksum and installing one binary each, which only invited a machine to
end up with `asz` of one version and the recorder of another. A version before 0.5.0 keeps the
-two-formula shape it was released with, and its formulae stay as they are; `asz-claude-code.rb`,
-the pointer to the newest, is deleted when 0.5.0 is added.
+two-formula shape it was released with, and its formulae stay as they are.
- `asz@VERSION.rb` for every released version from 0.5.0, and `asz@VERSION.rb` with
`asz-claude-code@VERSION.rb` for the versions before it, which stay as they were released,
@@ -37,9 +36,10 @@ its URL, because the repository is not named `homebrew-skywalking-ai-sessionizer
needs no trust: it names each formula in full, which Homebrew takes as trusting that one.
The user names one version or several. Each must be released: voted, on the download site or the
-archive, and its GitHub release promoted. 0.4.0 is the first version these templates fit; before
-it, the plugin's binary was not beside `asz` in the package. This runs on macOS with Homebrew. It
-changes nothing but a pull request to main in this repository.
+archive, and its GitHub release promoted. 0.5.0 is the first version the template fits, because
+it installs `asz-changes`, which a package before 0.5.0 does not hold. A version before 0.5.0 is in
+`Formula/` already, with the formulae it was released with; never write it again. This runs on
+macOS with Homebrew. It changes nothing but a pull request to main in this repository.
A local proxy can break HTTPS to Apache hosts, so the commands below go around it.
@@ -83,9 +83,7 @@ for v in <versions>; do
curl --noproxy '*' -fsSL -o "$W/$v/$g" "https://archive.apache.org/dist/skywalking/ai-sessionizer/$v/$g"
done
(cd "$W/$v" && shasum -a 512 -c "$f.sha512")
- # 0.5.0 renamed the recorder: asz-claude-plugin up to 0.4.x, asz-changes
- # from 0.5.0. An archive holds one of them beside asz, never both.
- tar -tvzf "$W/$v/$f" | awk '($NF == "asz" || $NF == "asz-changes" || $NF == "asz-claude-plugin") && $1 ~ /^-rwx/ {n++}
+ tar -tvzf "$W/$v/$f" | awk '($NF == "asz" || $NF == "asz-changes") && $1 ~ /^-rwx/ {n++}
$NF == "LICENSE" || $NF == "NOTICE" {m++} $NF ~ /^licenses\/./ {l = 1}
END {if (n != 2 || m != 2 || !l) {print "missing a file the formula installs"; exit 1}}'
done
@@ -145,7 +143,7 @@ tap=aszcheck/formulae
brew tap-new --no-git "$tap"
dir=$(brew --repository "$tap")/Formula
for v in <versions>; do cp "$W/formula-$v/asz@$v.rb" "$dir/"; done
-if [ "$newest" != "$current" ]; then cp "$W/formula-$newest/asz.rb" "$dir/"; git rm -q --ignore-unmatch "$dir/asz-claude-code.rb"; fi
+if [ "$newest" != "$current" ]; then cp "$W/formula-$newest/asz.rb" "$dir/"; fi
env -u http_proxy -u https_proxy -u all_proxy brew style "$tap"
for f in "$dir"/*.rb; do
n=$tap/$(basename "$f" .rb)
@@ -155,9 +153,7 @@ for f in "$dir"/*.rb; do
done
for v in <versions>; do
"$(brew --prefix "asz@$v")/bin/asz" version
- # The recorder is asz-changes from 0.5.0 and asz-claude-plugin before it.
- "$(brew --prefix "asz@$v")/bin/asz-changes" version ||
- "$(brew --prefix "asz@$v")/bin/asz-claude-plugin" version
+ "$(brew --prefix "asz@$v")/bin/asz-changes" version
done
```
@@ -175,7 +171,7 @@ brew untap "$tap"
```sh
mkdir -p Formula
for v in <versions>; do cp "$W/formula-$v/asz@$v.rb" Formula/; done
-if [ "$newest" != "$current" ]; then cp "$W/formula-$newest/asz.rb" Formula/; git rm -q --ignore-unmatch Formula/asz-claude-code.rb; fi
+if [ "$newest" != "$current" ]; then cp "$W/formula-$newest/asz.rb" Formula/; fi
git status --short
```
@@ -191,8 +187,8 @@ gh pr create --repo apache/skywalking-ai-sessionizer --base main --title "Homebr
```
The commit message and the pull request carry no AI attribution: no Co-Authored-By line and no
-"Generated with" line. Say in the pull request which versions were added, which version `asz` and
-`asz` install now, and that the check passed.
+"Generated with" line. Say in the pull request which versions were added, which version the `asz`
+formula installs now, and that the check passed.
## 8. After the merge
diff --git a/.claude/skills/pypi/SKILL.md b/.claude/skills/pypi/SKILL.md
index 9fbf3aa..aac15d3 100644
--- a/.claude/skills/pypi/SKILL.md
+++ b/.claude/skills/pypi/SKILL.md
@@ -1,6 +1,6 @@
---
name: pypi
-description: Publish a released version of the Apache SkyWalking AI Sessionizer LangChain plugin, apache-skywalking-asz-langchain, to PyPI. Downloads the voted source package, verifies it, builds the sdist and the wheel from plugins/langchain inside it, checks and installs them, and uploads them with twine. Use after a version is published.
+description: Publish a released version of the Apache SkyWalking AI Sessionizer LangChain plugin, apache-skywalking-asz-langchain, to PyPI. Says what a release manager sets up before the first upload, downloads the voted source package, verifies it, builds the sdist and the wheel from plugins/langchain inside it, checks and installs them, and uploads them with twine. Use after a version is published.
user-invocable: true
---
@@ -11,65 +11,123 @@ voted source package, and PyPI is a convenience channel written after the vote,
and the apt repository are. Nothing is uploaded for a candidate, and the version uploaded is the
released one, which `release.sh prepare` wrote into `plugins/langchain/pyproject.toml`.
+Unlike the Homebrew and apt skills, this one opens no pull request. It uploads, and an upload
+cannot be undone: PyPI never accepts a file name twice, even after the file is deleted.
+
Ask for the version when it is not given. Then:
-1. **Download the voted source package and verify it.** Every released version is on
- archive.apache.org; the newest is on downloads.apache.org too.
-
- ```sh
- v=VERSION
- pkg=apache-skywalking-ai-sessionizer-$v-src.tgz
- base=https://archive.apache.org/dist/skywalking/ai-sessionizer/$v
- W=$(mktemp -d); cd "$W"
- curl -fsSLO "$base/$pkg" && curl -fsSLO "$base/$pkg.sha512" && curl -fsSLO "$base/$pkg.asc"
- curl -fsSL https://downloads.apache.org/skywalking/KEYS | gpg --import
- shasum -a 512 -c "$pkg.sha512" && gpg --verify "$pkg.asc" "$pkg"
- tar -xzf "$pkg"
- ```
-
- Stop if either check fails. Nothing is built from a package that did not verify.
-
-2. **Build from the package, not from a checkout.** A checkout can hold what the vote did not.
-
- ```sh
- python3 -m venv build-env && . build-env/bin/activate
- pip install --quiet build twine
- python -m build --outdir dist "apache-skywalking-ai-sessionizer-$v-src/plugins/langchain"
- twine check dist/*
- ```
-
- `dist/` holds `apache_skywalking_asz_langchain-$v.tar.gz` and
- `apache_skywalking_asz_langchain-$v-py3-none-any.whl`. Check the version in the file names is
- `$v`, and that `unzip -l dist/*.whl` lists `LICENSE` and `NOTICE` under the `.dist-info`
- directory.
-
-3. **Install the wheel where nothing else is, and run it.** `asz-langchain status` exits 1 while
- the plugin is not enabled and 0 while it is, so the checks below say which exit code they
- expect.
-
- ```sh
- python3 -m venv try-env && . try-env/bin/activate
- pip install --quiet dist/*.whl
- asz-langchain status && { echo "enabled before enable"; exit 1; }
- asz-langchain enable && asz-langchain status || { echo "enable did not take"; exit 1; }
- asz-langchain disable && ! asz-langchain status || { echo "disable did not take"; exit 1; }
- deactivate
- ```
-
- `status` must say "not enabled", then "enabled at ...", then "not enabled" again. Stop if a
- line prints its message.
-
-4. **Upload.** The project's PyPI account owns the name; the release manager has a token for it.
-
- ```sh
- . build-env/bin/activate
- TWINE_USERNAME=__token__ TWINE_PASSWORD="$PYPI_TOKEN" twine upload dist/*
- ```
-
- PyPI refuses a file name it already holds, so a version is uploaded once; to correct one,
- release the next version.
-
-5. **Check it landed.** `pip index versions apache-skywalking-asz-langchain` lists `$v`, and
- `pip install apache-skywalking-asz-langchain==$v` in a fresh environment installs it.
-
-Then say what was uploaded and where, and clean up `$W`.
+## 0. Set up the upload
+
+The project `apache-skywalking-asz-langchain` is on PyPI since 0.5.0, whose upload created it. Only
+an owner or a maintainer of the project on PyPI can upload. A release manager who is neither asks
+an owner to add them.
+
+1. **A PyPI account with two-factor authentication.** PyPI refuses an upload from an account
+ without it.
+2. **An API token.** A token scoped to a project can only be made once the project exists. So the
+ upload that creates a project needs a token scoped to the whole account, as 0.5.0 did. Every
+ later upload uses a token scoped to `apache-skywalking-asz-langchain`. After the first upload,
+ replace the account token with a project token, and delete the account token.
+3. **The token in `SW_PYPI_TOKEN`,** exported from the shell profile, such as `~/.zprofile`. Then
+ start Claude Code. A session reads the profile once, when it starts, and never sees a variable
+ added later. Its permission check also refuses to read a token out of a file, so the token has
+ to be in the environment the session started with.
+
+Check it without printing it. Stop and tell the user when it is not set:
+
+```sh
+[ -n "$SW_PYPI_TOKEN" ] && echo "SW_PYPI_TOKEN is set, ${#SW_PYPI_TOKEN} characters"
+```
+
+Never print the token, and never write it into a file.
+
+## 1. Download the voted source package and verify it
+
+The newest version is on downloads.apache.org minutes after the move. archive.apache.org holds every
+version, but gets a new one hours later: for 0.5.0 it still answered 404 fifteen minutes after the
+move, and 200 when checked again four hours later. So try the download site first.
+
+```sh
+v=VERSION
+pkg=apache-skywalking-ai-sessionizer-$v-src.tgz
+W=$(mktemp -d); cd "$W"
+for f in "$pkg" "$pkg.sha512" "$pkg.asc"; do
+ curl --noproxy '*' -fsSLO "https://downloads.apache.org/skywalking/ai-sessionizer/$v/$f" ||
+ curl --noproxy '*' -fsSLO "https://archive.apache.org/dist/skywalking/ai-sessionizer/$v/$f"
+done
+curl --noproxy '*' -fsSL -o KEYS https://downloads.apache.org/skywalking/KEYS
+gpg --dearmor < KEYS > keys.gpg
+shasum -a 512 -c "$pkg.sha512" && gpgv --keyring "$W/keys.gpg" "$pkg.asc" "$pkg"
+tar -xzf "$pkg"
+```
+
+`gpgv` reads KEYS from a file of its own, so the user's keyring is left alone. `gpg --import` into
+a scratch directory under a long path hung on macOS, waiting to start `gpg-agent`. A local proxy can
+break HTTPS to Apache hosts, which is why the commands go around it.
+
+Stop if either check fails. Nothing is built from a package that did not verify.
+
+## 2. Build from the package, not from a checkout
+
+A checkout can hold what the vote did not.
+
+```sh
+python3 -m venv build-env && . build-env/bin/activate
+pip install --quiet build twine
+python -m build --outdir dist "apache-skywalking-ai-sessionizer-$v-src/plugins/langchain"
+twine check dist/*
+```
+
+`dist/` holds `apache_skywalking_asz_langchain-$v.tar.gz` and
+`apache_skywalking_asz_langchain-$v-py3-none-any.whl`. Check the version in the file names is
+`$v`, and that `unzip -l dist/*.whl` lists `LICENSE` and `NOTICE` under the `.dist-info`
+directory.
+
+## 3. Install the wheel where nothing else is, and run it
+
+`asz-langchain status` exits 1 while the plugin is not enabled and 0 while it is, so the checks
+below say which exit code they expect. `enable` writes only into the environment's own site
+directory.
+
+```sh
+python3 -m venv try-env && . try-env/bin/activate
+pip install --quiet dist/*.whl
+asz-langchain status && { echo "enabled before enable"; exit 1; }
+asz-langchain enable && asz-langchain status || { echo "enable did not take"; exit 1; }
+asz-langchain disable && ! asz-langchain status || { echo "disable did not take"; exit 1; }
+deactivate
+```
+
+`status` must say "not enabled", then "enabled at ...", then "not enabled" again. Stop if a
+line prints its message.
+
+## 4. Upload
+
+```sh
+. build-env/bin/activate
+TWINE_USERNAME=__token__ TWINE_PASSWORD="${SW_PYPI_TOKEN:?SW_PYPI_TOKEN is not set}" \
+ twine upload --non-interactive dist/*
+```
+
+`--non-interactive` makes a missing or refused token fail, rather than wait for a password prompt
+that nobody sees. To correct a version once it is uploaded, release the next version.
+
+## 5. Check it landed
+
+PyPI must hold the two files that were built, byte for byte, and they must install. Run this in
+the directory of the upload: a build run again from the same source gives other bytes, because the
+wheel and the sdist record the time they were built.
+
+```sh
+curl -fsSL "https://pypi.org/pypi/apache-skywalking-asz-langchain/$v/json" |
+ python3 -c 'import json, sys; [print(f["digests"]["sha256"] + " " + f["filename"]) for f in json.load(sys.stdin)["urls"]]'
+shasum -a 256 dist/*
+python3 -m venv pypi-env && . pypi-env/bin/activate
+pip install --quiet --no-cache-dir "apache-skywalking-asz-langchain==$v"
+pip index versions apache-skywalking-asz-langchain
+asz-langchain status; echo "exit $? (1: installed, not enabled)"
+deactivate
+```
+
+Then say what was uploaded and where, and clean up `$W`. After an upload made with a token scoped to
+the whole account, remind the user to replace it with a token scoped to the project.
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index d0b4ab9..d1627e4 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -39,3 +39,17 @@
the notification, `reports`, and a child that never came back has no `result_of`. LangChain's
nested agents and Claude Code's synchronous returns gain it; nothing is re-landed, and the next
round of an existing conversation adds it.
+
+## Release
+
+- The `pypi` skill says what a release manager sets up before an upload: two-factor
+ authentication, a token scoped to the whole account for the upload that creates a project and a
+ token scoped to the project after it, and that token in `SW_PYPI_TOKEN` in the environment
+ Claude Code starts with. It downloads from downloads.apache.org first, because
+ archive.apache.org still had no 0.5.0 fifteen minutes after the move. It verifies with `gpgv`,
+ which leaves the user's keyring alone, and checks that PyPI holds the files it built.
+- The `homebrew` skill no longer runs `git rm` inside the temporary tap, where git stops the check
+ whenever `asz.rb` moves. Its template fits 0.5.0 and later only.
+- The `apt` skill installs every older version in the index through the redirects, not only the
+ versions added. Adding a version moves the one that was newest to the archive, so its redirect
+ changes too.
diff --git a/docs/en/guides/how-to-release.md b/docs/en/guides/how-to-release.md
index d4e1e53..dc3398c 100644
--- a/docs/en/guides/how-to-release.md
+++ b/docs/en/guides/how-to-release.md
@@ -46,7 +46,7 @@ convenience made from its voted files.
| Target | What users get | Written by | When |
| --- | --- | --- | --- |
| The release directory, served by downloads.apache.org and its mirrors | the voted packages, each with its `.asc` and `.sha512` | [3. Publish](#3-publish) | right after the vote |
-| archive.apache.org | every released version, also after it leaves the release directory | Apache's infrastructure, from the release directory | by itself |
+| archive.apache.org | every released version, also after it leaves the release directory | Apache's infrastructure, from the release directory | by itself, hours after the move |
| The GitHub release `v$VERSION` | the same files | CI creates it on the tag push, [2. Candidate](#2-candidate) attaches to it, [3. Publish](#3-publish) promotes it | at the vote, then at Publish |
| The container image, `ghcr.io/apache/skywalking-ai-sessionizer:$VERSION` | `docker pull` | CI's `docker` job, on the released event | when Publish promotes the GitHub release |
| The downloads page and the documentation, `data/projects.yml` in apache/skywalking-website | links to the source package and the binary archives, and the docs of the tag | [The website](#the-website), a pull request | at least one hour after the move |
@@ -65,9 +65,10 @@ trademark policies. The Homebrew tap is this repository, and the apt repository
neither needs a new repository. A Scoop bucket under github.com/apache would be one, which the PMC
asks INFRA to create.
-The skills are in `.claude/skills/`, and run with Claude Code. Each checks what it wrote, and
-changes nothing but its pull request. Their files are what they write from: the `homebrew` skill
-holds the formula templates.
+The skills are in `.claude/skills/`, and run with Claude Code. Each checks what it wrote before it
+publishes it. The `homebrew` and `apt` skills change nothing but their pull request. The `pypi`
+skill uploads to PyPI, which cannot be undone. Their files are what they write from: the
+`homebrew` skill holds the formula template.
## The steps
@@ -1002,10 +1003,8 @@ After Publish, on macOS, run the `homebrew` skill with Claude Code and name the
downloads the voted packages and checks each against its `.sha512`, writes the version's formula
from its template, and runs every new formula through `brew style`, `brew audit --strict`,
`brew install` from the GitHub release, and `brew test`, removing what it installed. It moves
-`asz.rb` only forward, so adding an older version never moves it back, and it deletes
-`asz-claude-code.rb` when 0.5.0 is added.
-Then it opens a pull request to main in this repository. Anyone who tapped gets the version with
-`brew update` once it merges.
+`asz.rb` only forward, so adding an older version never moves it back. Then it opens a pull request
+to main in this repository. Anyone who tapped gets the version with `brew update` once it merges.
## apt
@@ -1031,12 +1030,18 @@ source package, and is published to PyPI as a convenience after the vote, as the
the apt repository are written after it. The version on PyPI is the released version, which
`prepare` writes into `plugins/langchain/pyproject.toml`; nothing is uploaded for a candidate.
+The upload of 0.5.0 created the project on PyPI. An owner or a maintainer of it there can upload,
+with an API token scoped to the project, in `SW_PYPI_TOKEN` in the environment Claude Code starts
+with. The `pypi` skill says how to set that up, and why the upload that creates a project needs a
+token scoped to the whole account.
+
After Publish, run the `pypi` skill with Claude Code and name the version. It downloads the voted
-source package and checks it against its `.sha512` and its `.asc` against KEYS, builds the sdist
-and the wheel from `plugins/langchain` inside it with `python -m build`, checks both with
-`twine check`, installs the wheel into a fresh environment and runs `asz-langchain status`, and
-uploads both with `twine upload` under the project's PyPI account. The wheel carries `LICENSE` and
-`NOTICE`, which `pyproject.toml` names.
+source package from downloads.apache.org, or from archive.apache.org for an older version, and
+checks it against its `.sha512` and its `.asc` against KEYS. It builds the sdist and the wheel from
+`plugins/langchain` inside it with `python -m build`, checks both with `twine check`, installs the
+wheel into a fresh environment and runs `asz-langchain status`, and uploads both with
+`twine upload`. Then it checks that PyPI holds the same two files and that they install. The wheel
+carries `LICENSE` and `NOTICE`, which `pyproject.toml` names.
## Scoop