← All posts

`npx pkg@latest` is a different program every launch

The usual way to configure an ACP agent is a line like npx some-acp-agent@latest. It also means the program you run is decided at launch: npx, bunx and pnpx resolve the package name against the npm registry every time they start, so the agent you open a session on tomorrow is whatever was published last.

Cydonia deals with this in two places: agents installed from the registry, and entries you write yourself.

Registry installs, once

Settings › Agents lists the ACP registry, the catalog of agents for the Agent Client Protocol, fetched from cdn.agentclientprotocol.com and cached for a day. Each agent in it is published at an exact version, and that version is what Cydonia installs.

The registry says npx for an npm package. Cydonia reads that as "install this package", not "run it through npx":

npm install --prefix ~/.local/share/cydonia/agents/<id> \
  --ignore-scripts --no-fund --no-audit <package>@<version>

The executable comes from the installed package's own bin field, and the entry written into settings.toml points straight at it. From then on the agent starts from that path, and npm is not involved. This was in the first pull request: installs land in $XDG_DATA_HOME/cydonia, defaulting to ~/.local/share/cydonia, "so npm never runs during a chat turn".

--ignore-scripts keeps a dependency's postinstall from running as you before the agent has ever been launched.

Where the registry offers both an npm package and a release archive for this machine, npm is taken. An archive is downloaded by the app, checked against its sha256 when the registry publishes one, and unpacked into the same directory.

Hand-written entries

You can still write an agent yourself:

[[agents]]
name = "pinned-agent"
command = "npx"
args = ["[email protected]"]

Since PR #6 (4 September), Cydonia checks this kind of entry when it reads settings.toml. If command is npx, bunx or pnpx, every argument that does not start with - has to be a package with a version after its @, and the version has to start with a digit. Scoped names are handled: the version separator is the last @ that is not the scope's leading one.

Spec Passes
[email protected] yes
@scope/[email protected] yes
some-acp-agent no
some-acp-agent@latest no
some-acp-agent@next no
some-acp-agent@^1.2 no
@scope/agent no

An entry that fails is dropped when settings load. The line stays in the file, where it can be read and fixed; it just never launches. Any other command, a binary on your path for instance, is not checked.

Nothing by default

A fresh install used to ship npx lines for Claude and Codex. Those offered two agents nobody had installed, and the first session on one fetched a package off npm and ran it. The default agent list is now empty. An agent arrives through Settings › Agents or a line you write, and until then nothing can spawn.

What is not covered

  • Pinning a package does not pin its dependency tree. npm install resolves the dependencies of [email protected] at install time.
  • The check applies to agent entries. MCP servers in mcp.toml are launched as written, npx lines included.

How to install an agent, from the registry or by hand, is in the docs.