Files
PaperClipAI/server
Jannes StubbemannandClaude Opus 5 a0bdf388af fix(agents): refuse to hire onto an adapter this instance cannot run (#10256)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Hiring an agent means choosing a harness (adapter) for it, and an
instance can declare which harnesses it actually runs through
`PAPERCLIP_ADAPTERS`, which `reconcileAdapterAvailability` turns into a
disabled set at boot
> - The hire and create routes validate the adapter type with
`assertKnownAdapterType`, which only asks whether the adapter is
REGISTERED — a disabled adapter passes
> - So an agent can be created on a harness the instance cannot run, and
the failure only appears later, per run, at lease time: `Adapter "..."
is not in the configured adapter registry`
> - By then the error is in a run log, minutes after the choice, with
nothing tying it back to the harness the user picked; the agent also
keeps accepting work it can never do
> - This pull request validates the hire and create paths against the
ENABLED set and refuses with a message that names the adapters that are
available
> - The benefit is that an impossible choice fails at the moment it is
made, in the words of the choice itself, instead of as a run failure the
user cannot act on

## Linked Issues or Issue Description

No existing issue; describing it here per the bug report template.

**What happened**

On an instance with a curated registry, a company's Chief of Staff was
hired on `cursor_cloud`, which that instance had disabled. The API
accepted the hire. Its first assignment run then failed:

```
Failed to acquire lease for environment "Kubernetes Sandbox" (sandbox): Adapter "cursor_cloud" is not in the configured adapter registry
```

and its automation run sat in `queued` for hours afterwards. Nothing in
the hire response, the agent detail view, or the agent's status
explained that this harness could never run.

**Expected behavior**

hiring on an adapter the instance has disabled is refused at hire time,
with a message naming the adapters that can be chosen.

**Steps to reproduce**

1. Start the server with a registry that omits an otherwise-registered
adapter, e.g. `PAPERCLIP_ADAPTERS` listing `claude_local` but not
`cursor_cloud`.
2. `POST /api/companies/:companyId/agents` with
`{"name":"CoS","adapterType":"cursor_cloud"}`.
3. The agent is created (201). Every run it attempts fails at lease time
with the message above.

**Paperclip version or commit**

master (`4c55f0d8d`).

## What Changed

- `server/src/routes/agents.ts`: adds `assertSelectableAdapterType`,
which extends `assertKnownAdapterType` with an enabled-set check and
throws `422 Adapter "<type>" is not available on this instance.
Available adapters: <list>`. The hire (`POST .../agent-hires`) and
create (`POST .../agents`) paths now use it.
- Routes that operate on an EXISTING agent keep
`assertKnownAdapterType`, so an agent already running on a
since-disabled adapter is unaffected — the same rule
`listEnabledServerAdapters` already documents ("hidden from selection,
still functional for agents that already use them").
- `server/src/__tests__/agent-adapter-validation-routes.test.ts`: mocks
the adapter-plugin store's disabled set (so the test never writes to a
real `~/.paperclip/adapter-settings.json`), and covers
refuse-when-disabled (including that the message names the alternatives
and that no agent is created) plus create-still-works-when-enabled.

## Verification

```
pnpm vitest run server/src/__tests__/agent-adapter-validation-routes.test.ts
```
13 tests pass, including the two new cases and the existing
unknown-adapter-type test.

Manual: disable an adapter (`PATCH /api/adapters/:type {"disabled":
true}` as an instance admin, or omit it from `PAPERCLIP_ADAPTERS` and
restart), then POST an agent with that `adapterType` — 422 naming the
available adapters, and no agent row is created.

## Risks

Low, and scoped to new selections:

- Automation that creates agents on a disabled adapter now gets a 422
where it previously got a 201 followed by runs that always failed. That
is the intended behavior change, and the message names the valid
choices.
- Existing agents, and every route that acts on an existing agent, are
untouched.
- The enabled set comes from the same store `GET /api/adapters` already
reports, so the API and the picker cannot disagree.

## Model Used

Claude Opus 5 (Anthropic), model id `claude-opus-5`, 1M context window,
extended thinking, with tool use and code execution via Claude Code.

## 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
(`upstream/adapter-selection-guard`) and contains no internal ticket id
- [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 (the
new helper documents the selection-vs-existing-agent rule)
- [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

Related: #10254 makes the adapter inventory readable during onboarding,
which is what lets the picker hide these adapters in the first place.
This PR is the server-side backstop for the same failure.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 16:43:40 -07:00
..