142 lines
7.9 KiB
Markdown
142 lines
7.9 KiB
Markdown
---
|
|
title: Claude Code
|
|
summary: 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)
|
|
- Either `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` in adapter or
|
|
environment env (or host env), or a Claude Code subscription login
|
|
available to the execution target
|
|
|
|
## 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 (default: `claude-opus-5`) |
|
|
| `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 |
|
|
|
|
## Default model
|
|
|
|
An omitted, empty, or whitespace-only `model` uses Claude Opus 5
|
|
(`claude-opus-5`) on both the CLI and ACP engines. This also applies to existing
|
|
agents with an unset model, including agents created through the API and agents
|
|
running in sandboxes. No database migration is needed. The editor shows the
|
|
Paperclip default and leaves the setting unset until you select a model.
|
|
|
|
An explicit `model` takes precedence over `ANTHROPIC_MODEL`. When only
|
|
`ANTHROPIC_MODEL` is configured, the adapter keeps that override. Bedrock and
|
|
Vertex configurations without an explicit model keep their provider-specific
|
|
default because those providers use different model IDs. Host environment
|
|
settings apply only to local targets when resolving the model.
|
|
|
|
The default does not change explicitly configured agent models or the separate
|
|
Paperclip Runner's qualified provider profiles.
|
|
|
|
## 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.
|
|
|
|
## Remote credential ownership
|
|
|
|
When no API key or `CLAUDE_CODE_OAUTH_TOKEN` is configured,
|
|
`claude_local` uses a snapshot-owns-auth topology for managed sandbox execution
|
|
targets. When the run uses a sandbox execution target and no explicit
|
|
`CLAUDE_CONFIG_DIR` is configured, Paperclip creates a remote
|
|
`CLAUDE_CONFIG_DIR` under the run's Claude runtime directory. It uploads
|
|
sanitized host-side settings such as `settings.json` and `CLAUDE.md`, but the
|
|
managed seed does not upload host Claude credential files.
|
|
|
|
After the seed is copied, the remote materialization command checks the
|
|
execution target's own `$HOME/.claude` directory. For each missing credential
|
|
file, it copies `.credentials.json` or `credentials.json` from that remote home
|
|
into the managed `CLAUDE_CONFIG_DIR`. That means credentials baked into the
|
|
sandbox image win for managed remote Claude runs.
|
|
|
|
Worked example: a sandbox image contains `$HOME/.claude/.credentials.json` from
|
|
its own Claude Code login. Paperclip starts a managed remote `claude_local` run,
|
|
uploads only the sanitized config seed, and sets `CLAUDE_CONFIG_DIR` to the
|
|
remote runtime config path. Because the managed config has no credential file,
|
|
the adapter copies the sandbox image's `$HOME/.claude/.credentials.json` into
|
|
that path before invoking Claude. The sandbox snapshot owns the credential for
|
|
the run.
|
|
|
|
This differs from [`codex_local`](/adapters/codex-local), where a
|
|
Paperclip-managed sandbox run uploads a host-owned `CODEX_HOME/auth.json` and
|
|
therefore shadows any Codex login already present inside the sandbox image.
|
|
|
|
For manual local CLI usage outside heartbeat runs (for example running as `claudecoder` directly), use:
|
|
|
|
```sh
|
|
npx 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 `CLAUDE_CODE_OAUTH_TOKEN` vs subscription login)
|
|
- A live hello probe (`claude --print - --output-format stream-json --verbose` with prompt `Respond with hello.`) to verify CLI readiness
|
|
|
|
The probe sees the same layered env as a real run: when an environment is
|
|
selected, its environment variables (secret refs included) are resolved and
|
|
merged under the adapter config's `env`, so environment-level auth is
|
|
reflected in the test result. A secret binding that is missing surfaces as
|
|
an `environment_env_binding_missing` failure instead of a silently passing
|
|
probe.
|