Conversation
Agent sandboxes and locked-down CI allow package-manager traffic by default while blocking direct binary downloads — Claude Code cloud sessions return 403 for release assets of repos not attached to the session, and skill-directory scanners flag piped installers while passing package managers. This makes getting `ant` onto such a machine a one-liner. Packaging only: nothing is rebuilt. The npm tarballs carry the same binaries the release workflow has already built and signed, verified against SHA256SUMS.txt and their ML-DSA-65 signatures before packaging, so they are byte-identical to the GitHub release assets. install.sh, install.ps1 and the release page are untouched. Six packages, following the mechanism esbuild established: a meta package plus one per platform, guarded by os/cpu and declared as optionalDependencies, so npm skips the four that do not match and a user downloads exactly one binary. A JS launcher resolves and execs it rather than a postinstall copying it into place, because install scripts cannot be relied on (below). Published from ant-cli-release.yml after the GitHub release, with provenance via OIDC and a dist-tag derived from the version: latest, beta, or rc — rc builds are installed by nothing today and must never move latest. `ant update` now recognises a package-manager-owned install and defers to it. Self-replacing an npm-managed binary left npm's metadata describing a file that was no longer there, so the next `npm update` silently rolled the user back. The check still reports whether a newer version exists, then prints `npm update -g @withautonomi/ant`; a second guard in ant-core refuses self-replacement for any other consumer of the crate. Detection is by resolved-path inspection rather than a build flag, since a build flag would produce a binary that is not byte-identical to the release asset. ant-core also falls back to a bootstrap peer list compiled into the binary when no bootstrap_peers.toml is present. npm 12 blocks package install scripts by default — allow-scripts is empty out of the box — so the step that copies that file into the config directory frequently does not run, which would otherwise leave a CLI that installs and reports its version but fails every network command, in exactly the sandboxes this change targets. Priority is unchanged for every existing install (explicit -b peers, then a devnet manifest, then the config file, then the embedded list), and an explicitly selected devnet manifest still errors rather than reaching for mainnet peers. bootstrap_peers.toml moved to ant-core/resources/ because include_str! cannot reach outside the crate and ant-core is published to crates.io; the release archives carry the same bytes as before. Verified end to end against a local registry: the documented one-liner installs and runs, the installed binary is byte-identical to the archive it came from, uninstalling leaves the config directory alone, a plain install with postinstall blocked still reaches the network, and `ant update` on the npm-installed binary reports install_method "npm". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012H3LsaffUstGkhHQdZeQD3
npm sets the `latest` dist-tag from a package's very first publish even when
`--tag` names something else, and later pre-release publishes move only their
own tag. Verified against a local registry:
first publish of 0.3.7-beta.1 with --tag beta
-> {'latest': '0.3.7-beta.1', 'beta': '0.3.7-beta.1'}
then publish 0.3.8-beta.1 with --tag beta
-> {'latest': '0.3.7-beta.1', 'beta': '0.3.8-beta.1'}
So if the npm distribution debuted on a beta, `npm install -g @withautonomi/ant`
— the command this repo's README documents — would hand everyone a beta build,
and `latest` would stay pinned to that one beta until a stable release moved it.
The stable channel must never serve a pre-release; that is the guarantee
install.sh and `ant update` already make, and npm should not be the exception.
The publish job now asks the registry whether the package exists and skips a
pre-release publish when it does not, with a warning rather than a failure. The
condition clears itself: at most the pre-releases before the first stable
release are skipped, and a stable publish with --tag latest reclaims the tag.
The registry is queried directly instead of through `npm view`, which resolves
a version spec (defaulting to `latest`) and so would conflate "no such package"
with "no latest tag".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DJ7ezqh78fsBWG71pyYmGq
jacderida
force-pushed
the
chrisoneil/v2-1152-distribute-the-ant-cli-via-npm
branch
from
September 8, 2026 16:11
e458702 to
0d1b820
Compare
`cargo doc` with `-D warnings`, as the Documentation CI job runs it, rejects an intra-doc link from the public `embedded_bootstrap_peers` to the private `EMBEDDED_BOOTSTRAP_PEERS`. Plain code formatting says the same thing without asking rustdoc to resolve a link it cannot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DJ7ezqh78fsBWG71pyYmGq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linear issue
Closes V2-1152
https://linear.app/autonominetwork/issue/V2-1152/distribute-the-ant-cli-via-npm
(mirrors #190)
Risk tier
Mostly packaging and CI, which alone would be T0. Proposed as T1 because two
client-side behaviours do change:
ant updaterefuses to self-replace on apackage-manager-owned install, and
antnow falls back to a bootstrap peer listcompiled into the binary when no config file is present. Neither touches node
behaviour, the wire protocol, stored-data format, or payments. The upgrade
mechanism is narrowed rather than altered — self-replacement is unchanged for
every install that exists today, and is only declined where it would corrupt a
package manager's view of its own files.
Compatibility
bootstrap_peers.tomlmoved fromresources/toant-core/resources/in the repo, but the release archives contain the samefile with the same bytes at the same path inside the archive, and both
installers copy it to the same config-directory destination as before.
ant_core::install(InstallMethod,detect,classify_path),ant_core::update::DeferredUpdate,ant_core::config::embedded_bootstrap_peers. No existing signature changed.ant_core::update::perform_updategains an early error return forpackage-manager-owned installs — a new failure mode on a path that previously
always attempted replacement.
Semver impact
Test evidence
Gates, mirroring what
ci.ymlruns:cargo fmt --all -- --checkcargo clippy --all-targets --all-features -- -D warningscargo test --lib --allcargo test -p ant-core --test unit_self_encryptcargo test -p ant-core --test merkle_unitcargo test -p ant-core --test daemon_integration --test node_add_integration -- --test-threads=1The devnet-dependent e2e suites (
e2e_chunk,e2e_data,e2e_file,e2e_payment,e2e_security,e2e_cost_estimate,e2e_adr0004,e2e_merkle) were not runlocally — they need a live devnet, and CI runs them in their own jobs. Nothing here
touches the upload, payment or chunk paths.
One flake seen and not reproduced:
cached_single::roundtrip_save_load_deletefailed once in a full-workspace run, then passed in isolation and in four
subsequent
--lib --allruns. It is a filesystem-receipt test untouched by thischange.
New unit tests: 11 in
ant-core/src/install.rscovering npm global, npm local,nested
node_modules, Windows paths,install.sh/cargo/local-build locations,and a directory whose name merely contains the substring; 2 in
ant-core/src/config.rscovering the embedded peer list parsing to usableaddresses, and an explicitly selected empty devnet manifest still erroring rather
than falling through to the embedded list.
End-to-end dry run
All six packages built by
npm/build-packages.shfrom staged release archives,served from a local verdaccio registry, installed with a scratch
npm_config_prefixand
XDG_CONFIG_HOME:npm install -g @withautonomi/antgives a workingant --versionant 0.3.6)cmpclean, sha2567d7847f1…)npm uninstall -gremoves everything except the config directoryBeyond the ACs:
ant -v chunk get …loggedNo bootstrap config file; using 7 embedded bootstrap peer(s)and connected.--allow-scripts, the postinstall wrotebootstrap_peers.tomlto theconfig directory, and a reinstall over a user-edited file left it untouched.
ant update --jsonon the npm-installed binary returned"install_method": "npm"with"update_command": "npm update -g @withautonomi/ant";ant update --forceexplained the refusal instead of silently doing nothing.dist-tag behaviour on a pre-release debut
Tested after the first commit, because the next release is a beta. npm sets
latestfrom a package's very first publish even when
--tagnames something else, andlater pre-release publishes move only their own tag:
A bare
npm install -g @withautonomi/antagainst that registry resolved to@withautonomi/ant@0.3.7-beta.1— i.e. a beta debut would have made the command thisPR documents serve a beta, with
lateststuck there until a stable release. Publishinga stable
0.3.7with--tag latestafterwards reclaimed the tag cleanly(
{'beta': '0.3.8-beta.1', 'latest': '0.3.7'}).The second commit adds a guard: a pre-release is not published when the package does not
yet exist on the registry, so the npm distribution debuts on a stable release. The
dist-tag mapping and gate were replayed offline across
0.3.7,-beta.N,-rc.N,-alpha.Nand-betamax.Nagainst both a 200 and a 404 registry response, and theregistry probe itself was checked against the real registry (
@withautonomi/ant→ 404,@stripe/cli→ 200).Not verified locally: cross-platform binaries (only the host target was built;
the other four packages carried a stand-in binary), the real npm registry, OIDC
provenance, and the publish job itself.
install-test.ymlgains fourworkflow_dispatchjobs — npm install on Linux/macOS/Windows plus an--ignore-scriptsjob — to cover those against a real published version.New dependency
None — no new Rust crates. The npm packages declare no runtime dependencies;
the launcher and postinstall use only Node's standard library. Node ≥18 becomes a
requirement for the npm install path only, and the release workflow gains
actions/setup-node.ADR
n/a — Tier 1.
Mitigation / rollback
The publish job is the last job in the release workflow and runs only after the
GitHub release has been created, so a failure there cannot affect the release. It
also skips itself with a warning when
NPM_TOKENis absent, so merging this beforethe token is configured cannot turn a release red. Backing the distribution out
means deprecating the npm packages and deleting the job; no released artifact
changes.
The two client-side behaviours are independently reversible. The
ant updatedeferral only triggers on a
node_modulespath — no existing install can reach it.The embedded peer fallback only runs where the previous code returned
Error::NoBootstrapPeers, so it can turn a hard failure into a success but neverchange the outcome of a currently-working configuration.
🤖 Generated with Claude Code
https://claude.ai/code/session_012H3LsaffUstGkhHQdZeQD3