Files
PaperClipAI/docs/adapters/claude-local.md
T
Devin FoleyandPaperclip bf982c8c83 Normalize adapter display labels (#8913)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Adapter names are part of the board-facing agent setup and
management experience.
> - The product now treats adapters as harnesses, while execution
environments are modeled separately.
> - Several built-in adapter labels still carried legacy local wording
from the older harness-by-environment model.
> - That wording makes the UI noisier and implies a distinction users no
longer need to reason about.
> - This pull request normalizes adapter display labels while keeping
persisted adapter type identifiers unchanged.
> - The benefit is clearer adapter selection and management copy without
a database migration.

## Linked Issues or Issue Description

No public GitHub issue was found for this exact cleanup.

Related public PRs:

- Supersedes #8910, an earlier branch for the same cleanup that did not
include the later docs/gateway/Cursor alignment.
- Refs #8819, which is related display-registry work for external
multi-segment adapter labels, but not a duplicate of this built-in label
cleanup.

Feature request details:

- Subsystem affected: Cross-cutting (`ui/`, `packages/adapters`, and
docs).
- Problem or motivation: user-facing adapter names include legacy local
qualifiers even though adapters map to harnesses and environments are
first-class elsewhere.
- Proposed solution: remove the legacy local wording from built-in
display labels, keep machine-readable adapter type ids unchanged, and
keep gateway disambiguation where it is useful.
- Alternatives considered: changing persisted adapter type ids was ruled
out because it would create migration and compatibility risk; one-off UI
replacements were ruled out because the display registry is already the
correct central label boundary.
- Roadmap alignment: this is small adapter UX polish, not a new
roadmap-level core feature.

## What Changed

- Updated the adapter display registry so known adapter labels are final
and no built-in local adapter renders a legacy local suffix.
- Preserved clean derived labels for unknown plugin local types while
keeping gateway disambiguation for unknown gateway types.
- Updated `AdapterManager` to prefer registry labels when the server
reports raw adapter type ids for built-ins.
- Removed legacy local wording from built-in adapter metadata labels in
UI and adapter packages.
- Aligned Cursor adapter metadata with the central display registry
label.
- Updated adapter docs and Storybook fixtures to match the new display
names.
- Added focused registry coverage for built-in labels and unknown plugin
suffix behavior.

## Verification

- `pnpm check:tokens`
- `git diff --check origin/master...fix/adapter-display-labels`
- Patch-addition scan for added secrets, private paths, and internal
links: no matches.
- GitHub duplicate search for open adapter-label/local-suffix issues and
PRs; #8910 was identified as the older superseded public PR.
- `pnpm exec vitest run
ui/src/adapters/adapter-display-registry.test.ts`
- `pnpm --filter @paperclipai/ui typecheck`
- Stale-label scan found no remaining user-facing display-label
suffixes; remaining local wording is operational/test terminology such
as adapter ids, docs about running locally, and test descriptions.

## Risks

Low risk. The change is display-label and documentation focused, and
adapter type ids remain unchanged. The main risk is ambiguous gateway
naming, mitigated by keeping explicit gateway labels where variants need
disambiguation.

## Model Used

OpenAI GPT-5 via Codex, tool-enabled coding agent in a local repository
workspace. Context window size is not exposed by this environment.

## 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-07-02 21:32:50 -07:00

5.0 KiB

title, summary
title summary
Claude Code Claude Code local adapter setup and configuration

The claude_local adapter runs Anthropic's Claude Code CLI locally. It supports session persistence, skills injection, and structured output parsing.

Prerequisites

  • Claude Code CLI installed (claude command available)
  • ANTHROPIC_API_KEY set in the environment or agent config

Configuration Fields

Field Type Required Description
cwd string Yes Working directory for the agent process (absolute path; created automatically if missing when permissions allow)
model string No Claude model to use (e.g. claude-opus-4-6)
promptTemplate string No Prompt used for all runs
env object No Environment variables (supports secret refs)
timeoutSec number No Process timeout (0 = no timeout)
graceSec number No Grace period before force-kill
maxTurnsPerRun number No Max agentic turns per heartbeat (defaults to 300)
dangerouslySkipPermissions boolean No Skip permission prompts (default: true); required for headless runs where interactive approval is impossible

Prompt Templates

Templates support {{variable}} substitution:

Variable Value
{{agentId}} Agent's ID
{{companyId}} Company ID
{{runId}} Current run ID
{{agent.name}} Agent's name
{{company.name}} Company name

Session Persistence

The adapter persists Claude Code session IDs between heartbeats. On the next wake, it resumes the existing conversation so the agent retains full context.

Session resume is cwd-aware: if the agent's working directory changed since the last run, a fresh session starts instead.

If resume fails with an unknown session error, the adapter automatically retries with a fresh session.

Poisoned previous_message_id (recovery)

Symptom in logs / issue thread:

API Error: 400 diagnostics.previous_message_id: must be the `id` from a prior /v1/messages response (starts with `msg_`)

What it means: the on-disk Claude Code transcript JSONL for that session contains a malformed (non-msg_-prefixed) previous_message_id. Anthropic's /v1/messages rejects every resume attempt against that transcript with a deterministic 400. Without guards, Paperclip would re-persist the same poisoned session id and the issue is stranded permanently — see RED-976 / RED-978.

What the adapter does automatically:

  1. Auto-rotate on resume. If a --resume attempt returns this 400, the adapter retries once with a fresh session, deletes the poisoned <session>.jsonl from the local Claude config dir (best effort), and uses the fresh session id going forward.
  2. Validate-before-persist. A result that carries this 400 never gets its session_id written back to the task session store, even if Claude Code emits one in the result event. The adapter returns sessionId: null, sessionParams: null, and errorCode: "claude_poisoned_previous_message_id".
  3. Clear-on-error. The adapter sets clearSession: true on the result, which causes the heartbeat service to drop any persisted session row for that issue (clearTaskSessions). The next continuation starts from a clean slate.

On-call checklist if you see this in production:

  • Confirm errorCode is claude_poisoned_previous_message_id in the run row — that means the guards fired correctly and the issue auto-recovers on the next heartbeat.
  • If the same issue still loops after one heartbeat, check that agentTaskSessions for that (agentId, taskKey) was cleared. If not, the adapter return value was lost (e.g. a malformed run finalization) — escalate; do not manually edit the row, file a child issue with the run id.
  • For remote execution targets (sandbox/SSH), the poisoned JSONL is on the remote and the adapter only logs the cleanup intent. The fresh-session retry still succeeds because it uses a new session id, and the server-side clearSession: true is authoritative regardless of remote disk state.

Skills Injection

The adapter creates a temporary directory with symlinks to Paperclip skills and passes it via --add-dir. This makes skills discoverable without polluting the agent's working directory.

For manual local CLI usage outside heartbeat runs (for example running as claudecoder directly), use:

pnpm paperclipai agent local-cli claudecoder --company-id <company-id>

This installs Paperclip skills in ~/.claude/skills, creates an agent API key, and prints shell exports to run as that agent.

Environment Test

Use the "Test Environment" button in the UI to validate the adapter config. It checks:

  • Claude CLI is installed and accessible
  • Working directory is absolute and available (auto-created if missing and permitted)
  • API key/auth mode hints (ANTHROPIC_API_KEY vs subscription login)
  • A live hello probe (claude --print - --output-format stream-json --verbose with prompt Respond with hello.) to verify CLI readiness