2026-03-03 14:46:16 -06:00
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT = " $( cd " $( dirname " $0 " ) /.. " && pwd ) "
2026-03-09 13:55:30 -05:00
# shellcheck source=./release-lib.sh
. " $REPO_ROOT /scripts/release-lib.sh "
2026-03-03 14:46:16 -06:00
CLI_DIR = " $REPO_ROOT /cli "
2026-03-17 14:08:55 -05:00
channel = ""
release_date = ""
2026-03-03 14:46:16 -06:00
dry_run = false
2026-03-17 14:08:55 -05:00
skip_verify = false
2026-03-18 07:50:33 -05:00
print_version_only = false
2026-03-17 14:08:55 -05:00
tag_name = ""
2026-03-03 14:46:16 -06:00
2026-03-09 08:49:42 -05:00
cleanup_on_exit = false
usage( ) {
cat <<'EOF'
Usage:
fix: harden release registry verification against npm lag (#4816)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Its release automation publishes canary packages to npm and then
validates the published registry state before considering the release
healthy
> - The failing canary run `25139465018` showed that npm can expose a
newly published version through version-specific endpoints before the
root package document has fully converged
> - That made a successful canary publish look like a failed release
because the verifier trusted stale root metadata too early
> - This pull request hardens the registry verification path by
preferring version-specific manifest checks, retrying
convergence-sensitive failures, and distinguishing permanent failures
from propagation lag
> - While validating that change in CI, a separate teardown race in
`heartbeat-stale-queue-invalidation.test.ts` surfaced and was hardened
so the PR could pass reliably
> - The benefit is that transient npm propagation lag no longer fails a
successful canary publish, while genuine registry-state and
dependency-integrity failures still stop the release flow promptly
## What Changed
- Hardened `scripts/verify-release-registry-state.mjs` so it prefers
version-specific manifest resolution over stale root metadata, adds
bounded registry-fetch timeouts, and classifies failures as retriable vs
non-retriable.
- Updated `scripts/release-lib.sh` and `scripts/release.sh` so
post-publish registry verification retries only convergence-sensitive
failures and reports immediate permanent failures clearly.
- Expanded `scripts/verify-release-registry-state.test.mjs` with
regression coverage for stale root metadata, fetch timeout behavior,
peer dependency range handling, non-retriable canary-latest cases, and
related verifier edge cases.
- Hardened
`server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts`
teardown to tolerate the late-comment foreign-key race that CI exposed
while validating this branch.
## Verification
- `pnpm run test:release-registry`
- `node --check scripts/verify-release-registry-state.mjs`
- `bash -n scripts/release.sh && bash -n scripts/release-lib.sh`
- PR checks passed on head `5c422600fc12acac61f6b7c267a4dc915df622b1`:
`policy`, `verify`, `e2e`, `security/snyk`, and `Greptile Review`
## Risks
- Low risk. The main behavioral changes are limited to release
automation and verifier retry semantics, plus a test-only teardown
hardening for a CI race.
> I checked [`ROADMAP.md`](ROADMAP.md). This is a narrow release bugfix
and does not overlap planned core feature work.
## Model Used
- OpenAI Codex via Paperclip `codex_local` with tool use and local code
execution enabled. This agent session runs on a GPT-5-class coding
model; the exact backend model ID/context window is not exposed by the
local adapter runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I have addressed all Greptile and reviewer comments before
requesting merge
2026-05-09 22:18:12 -07:00
./scripts/release.sh <canary| stable> [ --date YYYY-MM-DD] [ --dry-run] [ --skip-verify] [ --print-version]
2026-03-09 08:49:42 -05:00
Examples:
2026-03-17 14:08:55 -05:00
./scripts/release.sh canary
./scripts/release.sh canary --date 2026-03-17 --dry-run
./scripts/release.sh stable
./scripts/release.sh stable --date 2026-03-17 --dry-run
2026-03-18 07:50:33 -05:00
./scripts/release.sh stable --date 2026-03-18 --print-version
2026-03-09 08:49:42 -05:00
Notes:
2026-03-18 07:50:33 -05:00
- Stable versions use YYYY.MDD.P, where M is the UTC month, DD is the
zero-padded UTC day, and P is the same-day stable patch slot.
- Canary releases publish YYYY.MDD.P-canary.N under the npm dist-tag
"canary" and create the git tag canary/vYYYY.MDD.P-canary.N.
- Stable releases publish YYYY.MDD.P under the npm dist-tag "latest" and
create the git tag vYYYY.MDD.P.
- Stable release notes must already exist at releases/vYYYY.MDD.P.md.
2026-03-17 14:08:55 -05:00
- The script rewrites versions temporarily and restores the working tree on
exit. Tags always point at the original source commit, not a generated
release commit.
2026-03-09 08:49:42 -05:00
EOF
}
restore_publish_artifacts( ) {
2026-03-05 16:03:49 -06:00
if [ -f " $CLI_DIR /package.dev.json " ] ; then
mv " $CLI_DIR /package.dev.json " " $CLI_DIR /package.json "
fi
2026-03-09 08:49:42 -05:00
rm -f " $CLI_DIR /README.md "
2026-03-05 16:03:49 -06:00
rm -rf " $REPO_ROOT /server/ui-dist "
2026-03-09 08:49:42 -05:00
2026-03-05 16:03:49 -06:00
for pkg_dir in server packages/adapters/claude-local packages/adapters/codex-local; do
rm -rf " $REPO_ROOT / $pkg_dir /skills "
done
2026-03-09 08:49:42 -05:00
}
2026-03-05 16:03:49 -06:00
2026-03-09 08:49:42 -05:00
cleanup_release_state( ) {
restore_publish_artifacts
2026-03-09 10:11:46 -05:00
tracked_changes = " $( git -C " $REPO_ROOT " diff --name-only; git -C " $REPO_ROOT " diff --cached --name-only) "
if [ -n " $tracked_changes " ] ; then
printf '%s\n' " $tracked_changes " | sort -u | while IFS = read -r path; do
[ -z " $path " ] && continue
git -C " $REPO_ROOT " checkout -q HEAD -- " $path " || true
done
fi
untracked_changes = " $( git -C " $REPO_ROOT " ls-files --others --exclude-standard) "
if [ -n " $untracked_changes " ] ; then
printf '%s\n' " $untracked_changes " | while IFS = read -r path; do
[ -z " $path " ] && continue
if [ -d " $REPO_ROOT / $path " ] ; then
rm -rf " $REPO_ROOT / $path "
else
rm -f " $REPO_ROOT / $path "
fi
done
2026-03-05 16:03:49 -06:00
fi
2026-03-09 08:49:42 -05:00
}
2026-03-05 16:03:49 -06:00
2026-03-09 08:49:42 -05:00
set_cleanup_trap( ) {
cleanup_on_exit = true
trap cleanup_release_state EXIT
}
2026-03-17 14:08:55 -05:00
while [ $# -gt 0 ] ; do
case " $1 " in
canary| stable)
if [ -n " $channel " ] ; then
release_fail "only one release channel may be provided."
fi
channel = " $1 "
; ;
--date)
shift
[ $# -gt 0 ] || release_fail "--date requires YYYY-MM-DD."
release_date = " $1 "
; ;
--dry-run) dry_run = true ; ;
--skip-verify) skip_verify = true ; ;
2026-03-18 07:50:33 -05:00
--print-version) print_version_only = true ; ;
2026-03-17 14:08:55 -05:00
-h| --help)
usage
exit 0
; ;
*)
release_fail " unexpected argument: $1 "
; ;
esac
shift
done
2026-03-09 08:49:42 -05:00
2026-03-17 14:08:55 -05:00
[ -n " $channel " ] || {
usage
exit 1
2026-03-09 08:49:42 -05:00
}
2026-03-09 13:55:30 -05:00
PUBLISH_REMOTE = " $( resolve_release_remote) "
fetch_release_remote " $PUBLISH_REMOTE "
2026-03-17 14:08:55 -05:00
CURRENT_BRANCH = " $( git_current_branch) "
CURRENT_SHA = " $( git -C " $REPO_ROOT " rev-parse HEAD) "
2026-03-09 13:55:30 -05:00
LAST_STABLE_TAG = " $( get_last_stable_tag) "
CURRENT_STABLE_VERSION = " $( get_current_stable_version) "
2026-03-17 14:08:55 -05:00
RELEASE_DATE = " ${ release_date :- $( utc_date_iso) } "
2026-03-09 08:49:42 -05:00
2026-03-17 16:31:38 -05:00
PUBLIC_PACKAGE_INFO = " $( list_public_package_info) "
2026-03-18 07:50:33 -05:00
PUBLIC_PACKAGE_NAMES = ( )
while IFS = read -r package_name; do
[ -n " $package_name " ] || continue
PUBLIC_PACKAGE_NAMES += ( " $package_name " )
done < <( printf '%s\n' " $PUBLIC_PACKAGE_INFO " | cut -f2)
2026-03-17 16:31:38 -05:00
[ -n " $PUBLIC_PACKAGE_INFO " ] || release_fail "no public packages were found in the workspace."
2026-03-18 07:50:33 -05:00
TARGET_STABLE_VERSION = " $( next_stable_version " $RELEASE_DATE " " ${ PUBLIC_PACKAGE_NAMES [@] } " ) "
TARGET_PUBLISH_VERSION = " $TARGET_STABLE_VERSION "
DIST_TAG = "latest"
2026-03-17 14:08:55 -05:00
if [ " $channel " = "canary" ] ; then
require_on_master_branch
2026-03-17 16:31:38 -05:00
TARGET_PUBLISH_VERSION = " $( next_canary_version " $TARGET_STABLE_VERSION " " ${ PUBLIC_PACKAGE_NAMES [@] } " ) "
2026-03-17 14:08:55 -05:00
DIST_TAG = "canary"
tag_name = " $( canary_tag_name " $TARGET_PUBLISH_VERSION " ) "
else
tag_name = " $( stable_tag_name " $TARGET_STABLE_VERSION " ) "
2026-03-03 14:46:16 -06:00
fi
2026-03-18 07:50:33 -05:00
if [ " $print_version_only " = true ] ; then
printf '%s\n' " $TARGET_PUBLISH_VERSION "
exit 0
fi
2026-03-17 14:08:55 -05:00
NOTES_FILE = " $( release_notes_file " $TARGET_STABLE_VERSION " ) "
2026-03-09 13:55:30 -05:00
require_clean_worktree
2026-03-17 14:08:55 -05:00
require_npm_publish_auth " $dry_run "
2026-03-09 13:55:30 -05:00
2026-03-17 14:08:55 -05:00
if [ " $channel " = "stable" ] && [ ! -f " $NOTES_FILE " ] ; then
2026-03-09 13:55:30 -05:00
release_fail " stable release notes file is required at $NOTES_FILE before publishing stable. "
fi
2026-03-17 14:08:55 -05:00
if [ " $channel " = "canary" ] && [ -f " $NOTES_FILE " ] ; then
release_info " ✓ Stable release notes already exist at $NOTES_FILE "
2026-03-09 13:55:30 -05:00
fi
2026-03-17 14:08:55 -05:00
if git_local_tag_exists " $tag_name " || git_remote_tag_exists " $tag_name " " $PUBLISH_REMOTE " ; then
release_fail " git tag $tag_name already exists locally or on $PUBLISH_REMOTE . "
2026-03-09 09:06:45 -05:00
fi
2026-03-17 14:08:55 -05:00
while IFS = read -r package_name; do
[ -z " $package_name " ] && continue
if npm_package_version_exists " $package_name " " $TARGET_PUBLISH_VERSION " ; then
release_fail " npm version ${ package_name } @ ${ TARGET_PUBLISH_VERSION } already exists. "
fi
2026-03-17 16:31:38 -05:00
done <<< " $( printf '%s\n' " ${ PUBLIC_PACKAGE_NAMES [@] } " ) "
2026-03-03 14:46:16 -06:00
2026-03-09 13:55:30 -05:00
release_info ""
release_info "==> Release plan"
release_info " Remote: $PUBLISH_REMOTE "
2026-03-17 14:08:55 -05:00
release_info " Channel: $channel "
2026-03-09 13:55:30 -05:00
release_info " Current branch: ${ CURRENT_BRANCH :- <detached> } "
2026-03-17 14:08:55 -05:00
release_info " Source commit: $CURRENT_SHA "
2026-03-09 13:55:30 -05:00
release_info " Last stable tag: ${ LAST_STABLE_TAG :- <none> } "
release_info " Current stable version: $CURRENT_STABLE_VERSION "
2026-03-17 14:08:55 -05:00
release_info " Release date (UTC): $RELEASE_DATE "
release_info " Target stable version: $TARGET_STABLE_VERSION "
if [ " $channel " = "canary" ] ; then
2026-03-09 13:55:30 -05:00
release_info " Canary version: $TARGET_PUBLISH_VERSION "
2026-03-09 08:49:42 -05:00
else
2026-03-17 14:08:55 -05:00
release_info " Stable version: $TARGET_PUBLISH_VERSION "
2026-03-03 14:46:16 -06:00
fi
2026-03-17 14:08:55 -05:00
release_info " Dist-tag: $DIST_TAG "
release_info " Git tag: $tag_name "
if [ " $channel " = "stable" ] ; then
release_info " Release notes: $NOTES_FILE "
2026-03-03 14:46:16 -06:00
fi
2026-03-17 14:08:55 -05:00
set_cleanup_trap
2026-03-03 14:46:16 -06:00
ci: speed up PR verify workflow (#6137)
## Thinking Path
> - Paperclip orchestrates AI agents through a control-plane repo that
relies on GitHub Actions as part of its release and verification safety
net.
> - The PR workflow in `.github/workflows/pr.yml` is the core CI path
protecting pull requests before merge.
> - Baseline measurement work in [PAPA-335](/PAPA/issues/PAPA-335)
showed the old single `verify` job was the critical-path bottleneck,
with general tests and build serialized together.
> - Follow-up implementation in [PAPA-338](/PAPA/issues/PAPA-338) and
[PAPA-339](/PAPA/issues/PAPA-339) split that work into parallel lanes
and removed redundant clean-runner prebuild work.
> - [PAPA-340](/PAPA/issues/PAPA-340) now needs real post-change PR
workflow evidence, not local inference, to compare against the May 15,
2026 baseline and decide whether phase-2 work is still justified.
> - This pull request publishes the already-implemented CI speedup
branch so GitHub can run the actual `PR` workflow against it.
> - The benefit is that CI timing decisions are based on measured runs
from the exact workflow shape we intend to ship.
## What Changed
- Split the PR workflow so `policy` fans out into separate `Typecheck +
Release Registry`, grouped `General tests`, and `Build` jobs.
- Kept the serialized server matrix, canary dry run, and e2e jobs intact
while removing the old monolithic `verify` bottleneck.
- Reworked grouped general-test execution in
`scripts/run-vitest-stable.mjs` so the workflow can run balanced
non-serialized lanes.
- Replaced redundant clean-runner prebuild gates with the idempotent
`ensure-build-deps` path used by the relevant CI entrypoints.
## Verification
- `ruby -e "require 'yaml'; YAML.load_file('.github/workflows/pr.yml');
puts 'yaml-ok'"`
- `node scripts/run-vitest-stable.mjs --mode general --dry-run`
- `node scripts/run-vitest-stable.mjs --mode general --group
general-server --dry-run`
- `node scripts/run-vitest-stable.mjs --mode general --group
general-workspaces-a --dry-run`
- `node scripts/run-vitest-stable.mjs --mode general --group
general-workspaces-b --dry-run`
- `pnpm test:run:general -- --group general-workspaces-b`
- `pnpm test:run:general -- --group general-workspaces-a`
- `pnpm test:run:general -- --group general-server`
- `pnpm run typecheck:build-gaps`
- `pnpm --filter @paperclipai/plugin-hello-world-example typecheck`
## Risks
- Required-check and branch-protection settings may still reference the
old single `verify` job name.
- Parallel CI lanes can expose hidden ordering assumptions or
clean-runner bootstrap gaps that local grouped dry-runs did not surface.
- Because the branch is behind current `master`, merge conflicts or
unrelated upstream drift could affect the measured runtime until the
branch is rebased.
> Checked `ROADMAP.md`; this work is CI throughput maintenance for the
existing PR verification path, not duplicate feature work.
## Model Used
- OpenAI Codex via Paperclip `codex_local`, GPT-5-class coding agent
with repository read/write, shell execution, and GitHub CLI/tool use.
The runtime does not expose a more specific backend model ID in-session.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-16 11:28:25 -07:00
# The release flow already prepares ui/dist before packaging. Reuse that output
# so server prepack does not rebuild the UI a second time during preview/publish.
export PAPERCLIP_RELEASE_REUSE_UI_DIST = 1
2026-03-17 14:08:55 -05:00
if [ " $skip_verify " = false ] ; then
release_info ""
release_info "==> Step 1/7: Verification gate..."
cd " $REPO_ROOT "
pnpm -r typecheck
pnpm test:run
pnpm build
else
release_info ""
release_info "==> Step 1/7: Verification gate skipped (--skip-verify)"
2026-03-03 14:46:16 -06:00
fi
2026-03-17 14:08:55 -05:00
release_info ""
2026-03-17 15:30:30 -05:00
release_info "==> Step 2/7: Building workspace artifacts..."
2026-03-03 14:46:16 -06:00
cd " $REPO_ROOT "
2026-03-09 08:49:42 -05:00
pnpm build
Fix release packaging for standalone public packages (#4494)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies, and the
sandbox-provider work just moved E2B into a standalone publishable
plugin package.
> - That plugin is intentionally excluded from the root pnpm workspace
so it can model third-party install behavior without forcing lockfile
churn in the main repo.
> - The merged architecture change exposed a follow-up release problem:
the canary publish workflow tried to publish `@paperclipai/plugin-e2b`,
but the tarball had no `dist/` payload because standalone public
packages were not being built in the release path.
> - That means the release pipeline needed a packaging fix in core
release tooling, not another architectural change in the sandbox
provider itself.
> - This pull request adds a generic release step for public packages
that live outside the pnpm workspace, instead of hardcoding E2B-specific
behavior into the release script.
> - The benefit is that standalone publishable packages can be built and
packed correctly during release, including future sandbox-provider
plugins that follow the same pattern.
## What Changed
- Added `scripts/build-standalone-public-packages.mjs` to discover
public packages outside the pnpm workspace, run a clean package-local
install, and build them before publish.
- Updated `scripts/release.sh` to invoke that helper immediately after
the normal workspace build step.
- Kept the behavior generic by driving off the existing public package
map and pnpm workspace patterns rather than special-casing
`@paperclipai/plugin-e2b`.
## Verification
- `rm -rf packages/plugins/sandbox-providers/e2b/dist`
- `node ./scripts/build-standalone-public-packages.mjs`
- `cd packages/plugins/sandbox-providers/e2b && npm pack --dry-run`
- Confirm the tarball now includes the rebuilt `dist/` files instead of
only `README.md` / `package.json`
## Risks
- Low risk: this only changes the release build path for public packages
outside the pnpm workspace.
- The helper performs a clean package-local install for each standalone
public package, so release time may increase slightly as more such
packages are added.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- OpenAI Codex via `codex_local`
- Model ID: `gpt-5.4`
- Reasoning effort: `high`
- Context window observed in runtime session metadata: `258400` tokens
- Capabilities used: terminal tool execution, git, GitHub CLI, and local
build/test inspection
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-25 12:16:23 -07:00
node " $REPO_ROOT /scripts/build-standalone-public-packages.mjs "
2026-03-09 07:21:33 -05:00
bash " $REPO_ROOT /scripts/prepare-server-ui-dist.sh "
2026-03-03 16:06:12 -06:00
for pkg_dir in server packages/adapters/claude-local packages/adapters/codex-local; do
rm -rf " $REPO_ROOT / $pkg_dir /skills "
cp -r " $REPO_ROOT /skills " " $REPO_ROOT / $pkg_dir /skills "
done
2026-03-09 13:55:30 -05:00
release_info " ✓ Workspace build complete"
2026-03-03 14:46:16 -06:00
2026-03-17 15:30:30 -05:00
release_info ""
release_info "==> Step 3/7: Rewriting workspace versions..."
set_public_package_version " $TARGET_PUBLISH_VERSION "
release_info " ✓ Versioned workspace to $TARGET_PUBLISH_VERSION "
2026-03-09 13:55:30 -05:00
release_info ""
2026-03-17 14:08:55 -05:00
release_info "==> Step 4/7: Building publishable CLI bundle..."
2026-03-17 15:30:30 -05:00
" $REPO_ROOT /scripts/build-npm.sh " --skip-checks --skip-typecheck
2026-03-09 13:55:30 -05:00
release_info " ✓ CLI bundle ready"
2026-03-03 14:46:16 -06:00
2026-03-17 14:08:55 -05:00
VERSIONED_PACKAGE_INFO = " $( list_public_package_info) "
VERSION_IN_CLI_PACKAGE = " $( node -e " console.log(require(' $CLI_DIR /package.json').version) " ) "
if [ " $VERSION_IN_CLI_PACKAGE " != " $TARGET_PUBLISH_VERSION " ] ; then
release_fail " versioning drift detected. Expected $TARGET_PUBLISH_VERSION but found $VERSION_IN_CLI_PACKAGE . "
fi
2026-03-09 13:55:30 -05:00
release_info ""
2026-03-03 14:46:16 -06:00
if [ " $dry_run " = true ] ; then
2026-03-17 14:08:55 -05:00
release_info "==> Step 5/7: Previewing publish payloads (--dry-run)..."
while IFS = $'\t' read -r pkg_dir _pkg_name _pkg_version; do
2026-03-09 08:49:42 -05:00
[ -z " $pkg_dir " ] && continue
2026-03-09 13:55:30 -05:00
release_info " --- $pkg_dir --- "
2026-03-09 08:49:42 -05:00
cd " $REPO_ROOT / $pkg_dir "
2026-03-17 14:08:55 -05:00
pnpm publish --dry-run --no-git-checks --tag " $DIST_TAG " 2>& 1 | tail -3
done <<< " $VERSIONED_PACKAGE_INFO "
release_info " [dry-run] Would create git tag $tag_name on $CURRENT_SHA "
2026-03-03 14:46:16 -06:00
else
2026-03-17 14:08:55 -05:00
release_info "==> Step 5/7: Publishing packages to npm..."
while IFS = $'\t' read -r pkg_dir pkg_name pkg_version; do
[ -z " $pkg_dir " ] && continue
release_info " Publishing $pkg_name @ $pkg_version "
cd " $REPO_ROOT / $pkg_dir "
pnpm publish --no-git-checks --tag " $DIST_TAG " --access public
done <<< " $VERSIONED_PACKAGE_INFO "
release_info " ✓ Published all packages under dist-tag $DIST_TAG "
fi
2026-03-12 12:42:00 -05:00
2026-03-17 14:08:55 -05:00
release_info ""
if [ " $dry_run " = true ] ; then
release_info "==> Step 6/7: Skipping npm verification in dry-run mode..."
else
2026-04-29 15:56:20 -07:00
release_info "==> Step 6/7: Confirming npm package availability and dist-tag integrity..."
2026-03-12 12:42:00 -05:00
VERIFY_ATTEMPTS = " ${ NPM_PUBLISH_VERIFY_ATTEMPTS :- 12 } "
VERIFY_DELAY_SECONDS = " ${ NPM_PUBLISH_VERIFY_DELAY_SECONDS :- 5 } "
fix: harden release registry verification against npm lag (#4816)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Its release automation publishes canary packages to npm and then
validates the published registry state before considering the release
healthy
> - The failing canary run `25139465018` showed that npm can expose a
newly published version through version-specific endpoints before the
root package document has fully converged
> - That made a successful canary publish look like a failed release
because the verifier trusted stale root metadata too early
> - This pull request hardens the registry verification path by
preferring version-specific manifest checks, retrying
convergence-sensitive failures, and distinguishing permanent failures
from propagation lag
> - While validating that change in CI, a separate teardown race in
`heartbeat-stale-queue-invalidation.test.ts` surfaced and was hardened
so the PR could pass reliably
> - The benefit is that transient npm propagation lag no longer fails a
successful canary publish, while genuine registry-state and
dependency-integrity failures still stop the release flow promptly
## What Changed
- Hardened `scripts/verify-release-registry-state.mjs` so it prefers
version-specific manifest resolution over stale root metadata, adds
bounded registry-fetch timeouts, and classifies failures as retriable vs
non-retriable.
- Updated `scripts/release-lib.sh` and `scripts/release.sh` so
post-publish registry verification retries only convergence-sensitive
failures and reports immediate permanent failures clearly.
- Expanded `scripts/verify-release-registry-state.test.mjs` with
regression coverage for stale root metadata, fetch timeout behavior,
peer dependency range handling, non-retriable canary-latest cases, and
related verifier edge cases.
- Hardened
`server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts`
teardown to tolerate the late-comment foreign-key race that CI exposed
while validating this branch.
## Verification
- `pnpm run test:release-registry`
- `node --check scripts/verify-release-registry-state.mjs`
- `bash -n scripts/release.sh && bash -n scripts/release-lib.sh`
- PR checks passed on head `5c422600fc12acac61f6b7c267a4dc915df622b1`:
`policy`, `verify`, `e2e`, `security/snyk`, and `Greptile Review`
## Risks
- Low risk. The main behavioral changes are limited to release
automation and verifier retry semantics, plus a test-only teardown
hardening for a CI race.
> I checked [`ROADMAP.md`](ROADMAP.md). This is a narrow release bugfix
and does not overlap planned core feature work.
## Model Used
- OpenAI Codex via Paperclip `codex_local` with tool use and local code
execution enabled. This agent session runs on a GPT-5-class coding
model; the exact backend model ID/context window is not exposed by the
local adapter runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I have addressed all Greptile and reviewer comments before
requesting merge
2026-05-09 22:18:12 -07:00
REGISTRY_STATE_VERIFY_ATTEMPTS = " ${ NPM_REGISTRY_STATE_VERIFY_ATTEMPTS :- 12 } "
REGISTRY_STATE_VERIFY_DELAY_SECONDS = " ${ NPM_REGISTRY_STATE_VERIFY_DELAY_SECONDS :- 5 } "
2026-03-12 12:42:00 -05:00
MISSING_PUBLISHED_PACKAGES = ""
2026-03-17 14:08:55 -05:00
while IFS = $'\t' read -r _pkg_dir pkg_name pkg_version; do
2026-03-12 12:42:00 -05:00
[ -z " $pkg_name " ] && continue
release_info " Checking $pkg_name @ $pkg_version "
if wait_for_npm_package_version " $pkg_name " " $pkg_version " " $VERIFY_ATTEMPTS " " $VERIFY_DELAY_SECONDS " ; then
release_info " ✓ Found on npm"
continue
fi
if [ -n " $MISSING_PUBLISHED_PACKAGES " ] ; then
MISSING_PUBLISHED_PACKAGES = " ${ MISSING_PUBLISHED_PACKAGES } , "
fi
MISSING_PUBLISHED_PACKAGES = " ${ MISSING_PUBLISHED_PACKAGES } ${ pkg_name } @ ${ pkg_version } "
done <<< " $VERSIONED_PACKAGE_INFO "
2026-03-17 14:08:55 -05:00
[ -z " $MISSING_PUBLISHED_PACKAGES " ] || release_fail " publish completed but npm never exposed: $MISSING_PUBLISHED_PACKAGES "
2026-03-12 12:42:00 -05:00
release_info " ✓ Verified all versioned packages are available on npm"
2026-04-29 15:56:20 -07:00
verify_args = (
--channel " $channel "
--dist-tag " $DIST_TAG "
--target-version " $TARGET_PUBLISH_VERSION "
)
while IFS = $'\t' read -r _pkg_dir pkg_name _pkg_version; do
[ -z " $pkg_name " ] && continue
verify_args += ( --package " $pkg_name " )
done <<< " $VERSIONED_PACKAGE_INFO "
fix: harden release registry verification against npm lag (#4816)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Its release automation publishes canary packages to npm and then
validates the published registry state before considering the release
healthy
> - The failing canary run `25139465018` showed that npm can expose a
newly published version through version-specific endpoints before the
root package document has fully converged
> - That made a successful canary publish look like a failed release
because the verifier trusted stale root metadata too early
> - This pull request hardens the registry verification path by
preferring version-specific manifest checks, retrying
convergence-sensitive failures, and distinguishing permanent failures
from propagation lag
> - While validating that change in CI, a separate teardown race in
`heartbeat-stale-queue-invalidation.test.ts` surfaced and was hardened
so the PR could pass reliably
> - The benefit is that transient npm propagation lag no longer fails a
successful canary publish, while genuine registry-state and
dependency-integrity failures still stop the release flow promptly
## What Changed
- Hardened `scripts/verify-release-registry-state.mjs` so it prefers
version-specific manifest resolution over stale root metadata, adds
bounded registry-fetch timeouts, and classifies failures as retriable vs
non-retriable.
- Updated `scripts/release-lib.sh` and `scripts/release.sh` so
post-publish registry verification retries only convergence-sensitive
failures and reports immediate permanent failures clearly.
- Expanded `scripts/verify-release-registry-state.test.mjs` with
regression coverage for stale root metadata, fetch timeout behavior,
peer dependency range handling, non-retriable canary-latest cases, and
related verifier edge cases.
- Hardened
`server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts`
teardown to tolerate the late-comment foreign-key race that CI exposed
while validating this branch.
## Verification
- `pnpm run test:release-registry`
- `node --check scripts/verify-release-registry-state.mjs`
- `bash -n scripts/release.sh && bash -n scripts/release-lib.sh`
- PR checks passed on head `5c422600fc12acac61f6b7c267a4dc915df622b1`:
`policy`, `verify`, `e2e`, `security/snyk`, and `Greptile Review`
## Risks
- Low risk. The main behavioral changes are limited to release
automation and verifier retry semantics, plus a test-only teardown
hardening for a CI race.
> I checked [`ROADMAP.md`](ROADMAP.md). This is a narrow release bugfix
and does not overlap planned core feature work.
## Model Used
- OpenAI Codex via Paperclip `codex_local` with tool use and local code
execution enabled. This agent session runs on a GPT-5-class coding
model; the exact backend model ID/context window is not exposed by the
local adapter runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I have addressed all Greptile and reviewer comments before
requesting merge
2026-05-09 22:18:12 -07:00
release_info " Waiting for npm dist-tags and package metadata to converge..."
if wait_for_release_registry_state \
" $REGISTRY_STATE_VERIFY_ATTEMPTS " \
" $REGISTRY_STATE_VERIFY_DELAY_SECONDS " \
" ${ verify_args [@] } " ; then
:
else
verify_status = $?
if [ " $verify_status " -eq 2 ] ; then
release_fail " publish completed, but registry verification failed immediately for ${ TARGET_PUBLISH_VERSION } ; dist-tag state is wrong or requires operator intervention "
2026-05-09 11:40:02 -07:00
fi
fix: harden release registry verification against npm lag (#4816)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Its release automation publishes canary packages to npm and then
validates the published registry state before considering the release
healthy
> - The failing canary run `25139465018` showed that npm can expose a
newly published version through version-specific endpoints before the
root package document has fully converged
> - That made a successful canary publish look like a failed release
because the verifier trusted stale root metadata too early
> - This pull request hardens the registry verification path by
preferring version-specific manifest checks, retrying
convergence-sensitive failures, and distinguishing permanent failures
from propagation lag
> - While validating that change in CI, a separate teardown race in
`heartbeat-stale-queue-invalidation.test.ts` surfaced and was hardened
so the PR could pass reliably
> - The benefit is that transient npm propagation lag no longer fails a
successful canary publish, while genuine registry-state and
dependency-integrity failures still stop the release flow promptly
## What Changed
- Hardened `scripts/verify-release-registry-state.mjs` so it prefers
version-specific manifest resolution over stale root metadata, adds
bounded registry-fetch timeouts, and classifies failures as retriable vs
non-retriable.
- Updated `scripts/release-lib.sh` and `scripts/release.sh` so
post-publish registry verification retries only convergence-sensitive
failures and reports immediate permanent failures clearly.
- Expanded `scripts/verify-release-registry-state.test.mjs` with
regression coverage for stale root metadata, fetch timeout behavior,
peer dependency range handling, non-retriable canary-latest cases, and
related verifier edge cases.
- Hardened
`server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts`
teardown to tolerate the late-comment foreign-key race that CI exposed
while validating this branch.
## Verification
- `pnpm run test:release-registry`
- `node --check scripts/verify-release-registry-state.mjs`
- `bash -n scripts/release.sh && bash -n scripts/release-lib.sh`
- PR checks passed on head `5c422600fc12acac61f6b7c267a4dc915df622b1`:
`policy`, `verify`, `e2e`, `security/snyk`, and `Greptile Review`
## Risks
- Low risk. The main behavioral changes are limited to release
automation and verifier retry semantics, plus a test-only teardown
hardening for a CI race.
> I checked [`ROADMAP.md`](ROADMAP.md). This is a narrow release bugfix
and does not overlap planned core feature work.
## Model Used
- OpenAI Codex via Paperclip `codex_local` with tool use and local code
execution enabled. This agent session runs on a GPT-5-class coding
model; the exact backend model ID/context window is not exposed by the
local adapter runtime.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I have addressed all Greptile and reviewer comments before
requesting merge
2026-05-09 22:18:12 -07:00
release_fail " publish completed, but npm dist-tags or registry metadata never converged for ${ TARGET_PUBLISH_VERSION } "
fi
2026-03-03 14:46:16 -06:00
fi
2026-03-09 13:55:30 -05:00
release_info ""
2026-03-09 08:49:42 -05:00
if [ " $dry_run " = true ] ; then
2026-03-17 14:08:55 -05:00
release_info "==> Step 7/7: Dry run complete..."
2026-03-05 16:03:49 -06:00
else
2026-03-17 14:08:55 -05:00
release_info "==> Step 7/7: Creating git tag..."
git -C " $REPO_ROOT " tag " $tag_name " " $CURRENT_SHA "
release_info " ✓ Created tag $tag_name on $CURRENT_SHA "
2026-03-06 16:50:25 -06:00
fi
2026-03-09 13:55:30 -05:00
release_info ""
2026-03-09 08:49:42 -05:00
if [ " $dry_run " = true ] ; then
2026-03-17 14:08:55 -05:00
release_info " Dry run complete for $channel ${ TARGET_PUBLISH_VERSION } . "
else
if [ " $channel " = "canary" ] ; then
release_info " Published canary ${ TARGET_PUBLISH_VERSION } . "
release_info "Install with: npx paperclipai@canary onboard"
release_info " Next step: git push ${ PUBLISH_REMOTE } refs/tags/ ${ tag_name } "
2026-03-05 16:03:49 -06:00
else
2026-03-17 14:08:55 -05:00
release_info " Published stable ${ TARGET_PUBLISH_VERSION } . "
release_info "Next steps:"
release_info " git push ${ PUBLISH_REMOTE } refs/tags/ ${ tag_name } "
release_info " ./scripts/create-github-release.sh $TARGET_STABLE_VERSION "
2026-03-05 16:03:49 -06:00
fi
2026-03-03 14:46:16 -06:00
fi