Files
PaperClipAI/packages/plugins/sandbox-providers/boxd/README.md
T
Devin FoleyandPaperclip cf0ab4f7f4 chore: enable boxd plugin release publishing (#19020)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Sandbox provider plugins let those agents run in remote
environments.
> - The boxd provider exists, but its release entry excludes it from CI
publishing.
> - npm now has the `@paperclipai/plugin-boxd@0.0.0` bootstrap
placeholder.
> - This pull request enables CI publishing and documents npm
installation.
> - A pre-existing OpenCode fixture blocked CI because it qualified a
group-writable host Node binary.
> - This also gives that test its own Linux Node copy with strict
permissions.
> - Operators can install a real release after CI publishes it.

## Linked Issues or Issue Description

Related implementation:
https://github.com/paperclipai/paperclip/pull/17284.
No matching public issue or duplicate release-enrollment PR was found.

**What existing behavior does this improve?**

Publishing and installing the existing boxd sandbox provider.

**Subsystem affected**

`packages/plugins` and the npm release manifest.

**Current behavior**

`publishFromCi` is false. npm has only the `0.0.0` placeholder. The
README says the package is not published and requires a local-path
install.

**Proposed behavior**

Include boxd in CI releases. Document stable installation, explicit
published versions, and the placeholder limit.

**Reason and benefit**

Operators can install the provider through the existing npm plugin
installer after a real release ships.

**Breaking changes**

None. This changes release enrollment and documentation only.

**Additional context**

This follows the existing plugin and sandbox roadmap items. It adds no
new runtime capability.

## What Changed

- Set `publishFromCi: true` for `@paperclipai/plugin-boxd` in the
release manifest.
- Replace the README's unpublished-package notice with npm installation
guidance.
- Explain that the `0.0.0` placeholder has no plugin functionality and
that `latest` can point to it until the first stable release.
- Keep the local development instructions available.
- Fix the existing ingress-retry test to use an owned Linux Node copy
and explicit executable permissions. This follows the existing OpenCode
transport fixture. It does not change production launch checks.

## Verification

- Passed: `node --test scripts/release-package-map.test.mjs
scripts/check-release-package-bootstrap.test.mjs
scripts/bootstrap-npm-package.test.mjs` (31 tests).
- Passed: `node scripts/release-package-map.mjs check`.
- Passed: `PAPERCLIP_RELEASE_BOOTSTRAP_BASE_SHA=origin/master node
scripts/check-release-package-bootstrap.mjs
scripts/release-package-manifest.json
packages/plugins/sandbox-providers/boxd/README.md`.
- Passed: `git diff --check`.
- Confirmed that npm resolves `@paperclipai/plugin-boxd` to version
`0.0.0`.
- Full workspace typecheck, test suite, and build were not run locally.
CI checks remain required before merge.
- Initial CI failure: [native Runner
job](https://github.com/paperclipai/paperclip/actions/runs/41889969053/job/126785636239),
OpenCode ingress retry, `qualified OpenCode proxy command must not be
group- or world-writable`. The failing fixture is byte-identical to the
base commit `3a2249dd576`. It passed `process.execPath` to
qualification, while other OpenCode tests already use a private Linux
Node copy.
- Local focused run: `pnpm exec vitest run
server/src/services/native-runtime/native-ingress-bootstrap-retry.integration.test.ts`
built the real Rust Runner and passed the Codex case. The OpenCode case
stopped at `runner_remote_provider_artifact_incompatible: OpenCode proxy
path belongs to the controller host` on macOS (`/Users/...`). This does
not exercise the new Linux-only copy.
- An unchanged-base OpenCode comparison was attempted with `-t
"reacquires opencode ingress"`, but PostgreSQL setup failed with
`ENOSPC` before the case ran. The local baseline comparison is therefore
inconclusive. Task-owned Rust build outputs were cleaned to recover disk
space.
- At this stage Linux verification was pending. The successful final run
is recorded below.
- No new tests were needed. The existing release-tool tests cover
manifest enrollment, dependency ordering, and bootstrap checks.

- Current-head CI interruption: [workspaces-a shard
1/2](https://github.com/paperclipai/paperclip/actions/runs/41893808561/job/126799012853)
was cancelled after the runner received a shutdown signal. The log shows
no assertion failure. The job rerun request was rejected with HTTP 403
because the workflow was still running. The subsequent fixture
correction starts a new full CI run and supersedes that pending rerun.

- CI at `7fffa315`: [native Runner
job](https://github.com/paperclipai/paperclip/actions/runs/41893808561/job/126799011711)
passed executable permission checks, then failed because Node tried to
resolve `/memfd:paperclip-verified-launch (deleted)`. The private
executable was named `opencode-node`, which bypassed Runner's Node
interpreter recognition. The fixture now names its copy `node`, matching
the existing transport fixture and selecting the CommonJS descriptor
loader.
- Passed a local smoke test of the production CommonJS descriptor loader
using an open, unlinked script file. This is macOS evidence only; the
successful final Linux run below provides the complete ingress retry
evidence.

- Final verification on `bcacac542aa0a1b641b26c099c1e9fc4a1653ec4`: all
54 reported checks passed; three unrelated checks were skipped. [Native
Runner integration
job](https://github.com/paperclipai/paperclip/actions/runs/41895787712/job/126805844773)
passed, including the OpenCode ingress retry regression. The fresh full
run also passed the workspace shard that was interrupted on the previous
attempt. Greptile reports 5/5 for this exact head with no unresolved
review threads. Earlier failures and local verification limits remain
recorded above.

## Risks

- The maintainer confirmed that npm trusted publishing is configured for
this package. The first real CI publish will verify OIDC authentication.
- Merging enables publication in the next successful canary run. An
installation that uses `latest` can still resolve to the placeholder
until the first stable release includes this change.

## Model Used

OpenAI Codex, exact model ID `gpt-6.1-sol`, with xhigh reasoning effort,
repository tool use, and shell execution. The runtime does not expose
the context window size.

## 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 searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-04 11:36:04 -08:00

163 lines
7.6 KiB
Markdown

# `@paperclipai/plugin-boxd`
boxd sandbox provider for Paperclip. [boxd](https://boxd.sh) runs each sandbox
as an isolated KVM microVM. This package lives alongside E2B, Daytona and the
other sandbox providers, outside the root pnpm workspace. It uses the
[`@boxd-sh/sdk`](https://www.npmjs.com/package/@boxd-sh/sdk) package. Paperclip's
CI release pipeline publishes this plugin to npm.
## Install from npm
Once a stable release is available, install the package from the Plugins page
by name, or use the CLI:
```sh
paperclipai plugin install @paperclipai/plugin-boxd
```
Before the first stable release, the `latest` tag can still point to the
`0.0.0` bootstrap placeholder. That version reserves the package name and has
no plugin functionality. Check the published tags and choose a real release:
```sh
npm view @paperclipai/plugin-boxd dist-tags --json
paperclipai plugin install @paperclipai/plugin-boxd --version <published-version>
```
Replace `<published-version>` with the version shown for the desired tag, such
as `canary`. If no real release is available yet, use the local-path install
below. See the [publishing guide](../../../../doc/PUBLISHING.md) for the release
and bootstrap process.
## Build and install locally
Requires Node 24.11+, pnpm, and an installed Paperclip checkout.
```sh
cd packages/plugins/sandbox-providers/boxd
pnpm install --ignore-workspace --no-lockfile
pnpm typecheck
pnpm test
pnpm build
```
From the Paperclip checkout, with your instance running:
```sh
node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts plugin install /absolute/path/to/paperclip/packages/plugins/sandbox-providers/boxd
node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts plugin inspect paperclip.boxd-sandbox-provider
```
## Configure an environment
Configure boxd under **Instance Settings -> Environments**, not from the
plugin page. Select the `boxd` provider.
- `apiKey`: a boxd API key. Create one with
`boxd auth keys create paperclip --org <org>`. Each key belongs to one org,
and machines are created in that org. Paperclip saves a pasted key as a
company secret. `BOXD_API_KEY` on the Paperclip host is an optional fallback
when the environment has no key.
- `reuseLease`: default `false`. With `false`, the plugin deletes the machine
when the run ends. With `true`, the plugin suspends the machine when the run
ends, and the next run on the same issue resumes it. Disk, memory and running
processes stay. The provider lease ID is the machine ID, so it stays the same
across resumes.
- `snapshot`: optional snapshot name or ID. Each run restores this snapshot,
for example a machine with your tools and repository already in place.
- `vcpu`: optional machine size for fresh machines: 1 vCPU/4 GB, 2/8 GB,
4/16 GB or 8/32 GB. A blank value uses the org default. A snapshot keeps its
own size.
- `org`: optional org name or ID. A blank value uses the API key's org.
- `namePrefix`: machine name prefix, `paperclip` by default.
- `timeoutMs`: default command timeout, 3600000 ms by default.
The default boxd image is Ubuntu 24.04 with Node 24, `tar`, `git`, Docker,
Claude Code, Codex and OpenCode on the PATH. It meets
[the sandbox runtime requirements](../SANDBOX-REQUIREMENTS.md) without a
setup step. Use a snapshot to add other agent CLIs.
The probe creates a machine, prepares the workspace and deletes the machine.
It uses real boxd resources. The probe machine also has a 15-minute boxd delete
timer, and a probe whose machine could not be deleted reports a failure.
## Implemented behavior
- Every machine is created with `isolated: true`. An isolated machine has no
metadata endpoint, no in-VM `boxd` CLI and no boxd integrations, and other
machines in the org cannot reach it over the private network. The API key
stays in the plugin worker and never enters the machine.
- Machines carry the labels `paperclip-provider=boxd`,
`paperclip-environment=<id>` and `paperclip-run=<id>`. Use
`boxd machine list --label paperclip-provider=boxd` to find them.
- Commands run through `sh` in the workspace, with quoted arguments,
per-command environment, staged stdin, and separate stdout and stderr. A
command timeout ends the remote process group and returns `timedOut: true`.
- Stdin is staged with boxd's file API in a file only the machine user can
read, and removed after the command. The workspace directory is created the
same way. A machine whose boxd agent predates the file API, such as one
restored from an older snapshot, falls back to the older upload and `mkdir`
calls.
- A failed acquisition deletes its machine. If boxd cannot confirm the
deletion, the plugin throws `PluginEnvironmentCreationCleanupError` with the
machine ID, so the host records it and retries the teardown. The destroy hook
checks that the machine's labels match before it deletes it.
- A command on a stopped machine starts the machine and runs the command one
more time. A suspended or hibernated machine wakes on the command.
- Release deletes the machine and returns a receipt only after boxd no longer
lists it. With `reuseLease`, release suspends the machine. A release that
cancels active work stops the machine and keeps its disk.
- `onEnvironmentStopLease` stops the machine and keeps its disk.
`onEnvironmentInspectLease` reads the machine state and does not change it.
- When the host requests a lease expiry, the plugin sets a boxd delete timer
at or before that time and returns the expiry on the lease.
- The plugin keeps one SDK client for each API key, so commands do not
exchange the key for a new session token each time.
## Capability boundaries
Interactive login PTYs, template capture, native file sync, persistent
sessions, incremental output, duplex channels and runner ingress are not
advertised. Workspace transfer uses the host's base64-over-exec fallback.
boxd has one region (EU), amd64 machines only, and no GPUs.
## Opt-in live tests
Default tests use a mocked SDK and do not contact boxd. To run against boxd,
export `BOXD_API_KEY` (and optionally `BOXD_ORG`), then run:
```sh
BOXD_LIVE_TEST=1 pnpm test
```
The live tests run every hook against boxd. They check:
- config validation, and a probe with a sized machine that deletes the machine
afterwards (a bad API key returns a failed probe);
- the labels, the isolation and the org of an acquired machine;
- stdin, env, argument quoting, exit codes and stderr, and the removal of the
staged stdin file;
- an empty stdin, and a staged stdin file that only the machine user can read;
- a 5 MiB round trip through the base64-over-exec path;
- that a command timeout returns `timedOut` and ends the remote processes;
- suspend and resume with a file and a running process kept, stop and resume
with the file kept, and inspect in each state;
- a command on a stopped machine, which starts the machine and runs;
- resume and stop of a hibernated machine, stop of a suspended machine, and a
release with `cancelActiveWork` or `stop_and_retain`;
- a release without reuse, which deletes the machine;
- a `requestedExpiresAt` that boxd enforces by deleting the machine;
- a run restored from a snapshot, and a fresh machine at the configured size;
- that resume after destroy reports an expired lease, and that no test
machines are left.
Each test deletes its machines in `finally`. The tests print timings and no
credentials. A full run takes about two minutes.
## Optional managed-image inclusion
The bundled catalog key is `boxd`. Include the `boxd` directory in the Docker
`CLOUD_BUNDLED_PLUGINS` build argument, then include `boxd` in the managed
configuration's `plugins.autoInstall` list. The catalog entry does not
auto-install the plugin or change the default image.