Cut boring-ui-v2 Release + Update Global CLI
SkillCloud & infraCut a boring-ui-v2 npm release from main and update the local/global boring-ui CLI install. Use when Julien says cut a release, new release, publish boring-ui, update global boring-ui CLI, or install latest boring-ui CLI.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Cut boring-ui-v2 Release + Update Global CLI skill
What this skill tells your AI
The instructions your AI receives, as published by hachej/boring-ui in .agents/skill-library/devops-cut-boring-ui-release/SKILL.md and read by ahel’s review.
Use this skill for one job: release the current main of boring-ui-v2 to npm via GitHub Releases, then install that exact CLI version globally on this machine.
Non-negotiables
- Work from
mainonly. - Do not overwrite or stash user/agent dirty work. If the current worktree is dirty or not on
main, create a clean temporary worktree. - Do not use destructive commands (
git reset --hard,git clean -fd, force push). - Do not run
boring-ui --versionas a version check: this CLI may start a server. Use package-manager metadata instead. - Do not install
@latestuntil npm confirms the new version is visible. - After installing the global CLI, restart already-running global
boring-uiservers; long-lived Node processes keep the old package code in memory. If the server is a systemd user service (boring-ui-workspaces.service), restart it withsystemctl --user restart— do notkill+nohupa systemd-managed process (it races the auto-restart). After restarting, wait until the workspace route serves HTTP 200 again before declaring the step done. - If any release workflow fails, stop and report the failed run URL.
1. Prepare a clean main worktree
From anywhere inside the repo:
git fetch origin main --tags
If the active worktree is clean and already on main:
git switch main
git merge --ff-only origin/main
If the active worktree is dirty or on another branch, use a temporary worktree instead:
release_dir=$(mktemp -d /tmp/boring-ui-v2-release.XXXXXX)
rmdir "$release_dir"
git update-ref refs/heads/main origin/main
git worktree add "$release_dir" main
cd "$release_dir"
Confirm release preconditions:
git status --short --branch
test "$(git branch --show-current)" = main
test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
2. Check the current and next package versions
before=$(node -p "require('./packages/cli/package.json').version")
echo "current CLI package version: $before"
Use patch unless Julien explicitly asks for minor or major.
3. Run release gates
For normal releases, run:
pnpm lint
pnpm typecheck
pnpm test
If this is an urgent hotfix and Julien accepts a narrower gate, run at minimum:
pnpm --filter @hachej/boring-workspace run typecheck
pnpm audit:publish-manifests
Record exactly which gates ran.
4. Cut the release
./scripts/cut-release.sh patch
For non-patch releases:
./scripts/cut-release.sh minor
./scripts/cut-release.sh major
The script bumps all publishable package versions, commits chore(release): bump packages to <version>, pushes main, and creates a GitHub Release. The GitHub release triggers .github/workflows/release.yml, which publishes to npm.
Capture the new version:
after=$(node -p "require('./packages/cli/package.json').version")
echo "released CLI package version: $after"
5. Wait for npm publish workflow
Find the run triggered by the release tag and wait for it:
tag="v$after"
gh run list --workflow release.yml --branch main --limit 5
run_id=$(gh run list --workflow release.yml --json databaseId,headBranch,displayTitle,createdAt --jq '.[] | select(.displayTitle | contains("'"$tag"'")) | .databaseId' | head -1)
test -n "$run_id"
gh run watch "$run_id" --exit-status
If the run is not found by title, open the Actions page and identify the newest release workflow:
gh run list --workflow release.yml --limit 10
Stop if the workflow fails.
6. Wait until npm sees the exact CLI version
for i in $(seq 1 30); do
npm_version=$(npm view @hachej/boring-ui-cli version 2>/dev/null || true)
if [ "$npm_version" = "$after" ]; then
echo "npm has @hachej/boring-ui-cli@$after"
break
fi
echo "waiting for npm: saw '$npm_version', want '$after'"
sleep 10
done
test "$(npm view @hachej/boring-ui-cli version)" = "$after"
7. Update the global boring-ui CLI install
This machine currently uses npm global installs for boring-ui. Prefer npm unless inspection shows pnpm owns the global package.
Check current install:
command -v boring-ui || true
npm list -g --depth=0 @hachej/boring-ui-cli || true
pnpm list -g --depth=0 2>/dev/null | grep '@hachej/boring-ui-cli' || true
Install the exact released version:
npm install -g @hachej/boring-ui-cli@"$after"
Verify with package metadata, not by starting the CLI:
npm list -g --depth=0 @hachej/boring-ui-cli
node -p "require(require('child_process').execSync('npm root -g').toString().trim() + '/@hachej/boring-ui-cli/package.json').version"
command -v boring-ui
8. Restart running global boring-ui servers
A running node /home/ubuntu/.npm-global/bin/boring-ui ... process keeps the old CLI code in memory after npm install -g. Restart any global workspace servers that should pick up the new release.
Restarting drops live workspace sessions for ~30s while the runtime re-provisions — anyone on a …/workspace/<id> URL during that window sees "Workspace setup failed / NetworkError". That is expected and transient; this step is not done until the workspace route is serving again (see the readiness wait below). Restart at a quiet moment when you can.
8a. Prefer systemd if the hub is a managed service
The standard hub on port 5213 is usually the systemd user service boring-ui-workspaces.service. If it is, restart through systemd — never kill + nohup it by hand. systemd has Restart= on this unit, so a manual kill makes systemd respawn the old process while your nohup also spawns one, and the two race for the port (EADDRINUSE), leaving an orphan you don't control.
if systemctl --user list-units --type=service --all 2>/dev/null | grep -q 'boring-ui-workspaces.service'; then
systemctl --user restart boring-ui-workspaces.service
systemctl --user --no-pager -p ActiveState,SubState,MainPID,NRestarts show boring-ui-workspaces.service
managed=systemd
fi
A high NRestarts (hundreds/thousands) or a previous Result=oom-kill / status=9/KILL in journalctl --user -u boring-ui-workspaces.service means the hub is in a chronic crash/OOM loop — flag it in your report; it is a pre-existing problem, not your release.
8b. Manual restart only if NOT systemd-managed
if [ "${managed:-}" != systemd ]; then
pid=$(ss -ltnp 'sport = :5213' 2>/dev/null | sed -n 's/.*pid=\([0-9][0-9]*\).*/\1/p' | head -1)
if [ -n "$pid" ]; then
ps -p "$pid" -o pid,lstart,cmd
cwd=$(readlink "/proc/$pid/cwd")
kill -TERM "$pid"
for i in $(seq 1 20); do kill -0 "$pid" 2>/dev/null || break; sleep 0.5; done
if kill -0 "$pid" 2>/dev/null; then kill -KILL "$pid"; fi
else
cwd=/home/ubuntu/projects/boring-ui-v2
fi
log=/tmp/boring-ui-global-5213.log
cd "$cwd"
nohup boring-ui workspaces --port 5213 --host 0.0.0.0 > "$log" 2>&1 &
new_pid=$!
for i in $(seq 1 40); do
ss -ltnp 'sport = :5213' 2>/dev/null | grep -q "pid=$new_pid" && break
kill -0 "$new_pid" 2>/dev/null || { tail -80 "$log"; exit 1; }
sleep 0.5
done
ps -p "$new_pid" -o pid,lstart,cmd
fi
8c. Wait until the workspace route is actually ready
The port listening is not enough — the per-workspace runtime re-provisions on first access, and reporting success before that completes is what leaves users with "NetworkError". Wait for each configured workspace to serve, and confirm the hub logged it ready:
# Each workspace id from the hub config; adjust the grep if the path differs.
for slug in $(grep -oE 'workspace/[A-Za-z0-9_-]+' /home/ubuntu/.boring-ui/workspaces.yaml 2>/dev/null | sort -u); do
url="http://localhost:5213/$slug"
for i in $(seq 1 60); do
code=$(curl -s -o /dev/null -w '%{http_code}' -m 8 "$url" 2>/dev/null || echo 000)
[ "$code" = 200 ] && { echo "$slug ready (HTTP 200)"; break; }
sleep 2
done
done
journalctl --user -u boring-ui-workspaces.service --no-pager -n 15 | grep -iE 'ready|error|provision' || true
ss -ltnp 'sport = :5213'
If other global boring-ui processes are running, inspect and restart them intentionally rather than killing unrelated dev servers:
ps -eo pid,lstart,cmd | grep '/.npm-global/bin/boring-ui' | grep -v grep || true
9. Final response checklist
Report:
- release version and tag
- release commit SHA
- GitHub Release URL
- release workflow status/run URL
- npm version observed
- global CLI package version installed
- restarted global server PID(s), port(s), and log path(s); how it was restarted (systemd vs manual)
- confirmation each workspace route serves HTTP 200 after the restart
- any hub instability observed (high
NRestarts, OOM kills) — flagged as pre-existing - gates run
Signals
- GitHub stars
- 46
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
devops-cut-boring-ui-release- Source
- github.com/hachej/boring-ui