@molecule/api-agent-runtime-claude-code
Provider bond · agent-run · API (Node) · v1.1.5 · Apache-2.0
Claude Code agent runtime — unattended runs in ephemeral cloud sandboxes; artifacts-only return, per-run credentials, deny-by-default egress
npm install @molecule/api-agent-runtime-claude-codenpm · Source on GitHub · Implements @molecule/api-agent-run
How it works
@molecule/api-agent-runtime-claude-code is a provider bond on the API (Node) side: it implements the agent-run core interface (@molecule/api-agent-run) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
Works with: @molecule/api-agent-run, @molecule/api-ai-tools, @molecule/api-code-sandbox
Reference
Claude Code agent runtime — unattended runs in ephemeral cloud sandboxes; artifacts-only return, per-run credentials, deny-by-default egress
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Type
provider
Installation
npm install @molecule/api-agent-runtime-claude-code @molecule/api-agent-run @molecule/api-ai-tools @molecule/api-code-sandbox
API
Interfaces
ClaudeCodeRuntimeConfig
Options for createProvider.
interface ClaudeCodeRuntimeConfig extends AgentRunConfig {
/**
* npm package spec for the Claude Code CLI, installed at run start from the
* egress-allowed npm registry. Default
* `@anthropic-ai/claude-code@2.1.289` — PINNED EXACTLY, never `@latest`:
* its postinstall runs in the sandbox, so the version that runs there must
* be a deliberate choice.
*/
cliPackage?: string
/**
* Model id passed to the CLI when a spec names none. This is a CATALOG id
* (`claude-sonnet-5-5`); the runtime maps catalog ids to the CLI's model
* argument. Default `claude-sonnet-5-5`.
*/
defaultModel?: string
/**
* Extra seconds granted to the ENVIRONMENT beyond the task budget — the
* clone + CLI install happen before the agent starts, and the artifact
* collection happens after. Default 240000 (4 minutes).
*/
setupSlackMs?: number
}
Classes
ClaudeCodeAgentRuntime
Claude Code agent runtime backed by an ephemeral cloud sandbox.
class ClaudeCodeAgentRuntime implements AgentRuntimeProvider {
readonly name = 'claude-code'
private readonly defaults: Required<Omit<ClaudeCodeRuntimeConfig, 'cliPackage'>> & {
cliPackage: string
}
constructor(config: ClaudeCodeRuntimeConfig = {}) {
this.defaults = {
timeoutMs: config.timeoutMs ?? 600_000,
cliPackage: config.cliPackage ?? '@anthropic-ai/claude-code@2.1.289',
defaultModel: config.defaultModel ?? 'claude-sonnet-5-5',
setupSlackMs: config.setupSlackMs ?? 240_000,
}
}
async run(spec: AgentRunSpec, opts: AgentRunOptions): Promise<AgentRunArtifact> {
const provider = requireSandboxProvider()
const totalBudgetMs = spec.timeoutMs ?? this.defaults.timeoutMs
const sandboxBudgetMs = totalBudgetMs + this.defaults.setupSlackMs
const started = Date.now()
const logs: string[] = []
const log = (line: string): void => {
logs.push(line)
try {
opts.onLog?.(line)
} catch (_error) {}
}
const allowOut = [...new Set([...RUNTIME_ALLOWED_HOSTS, ...(spec.allowedHosts ?? [])])]
const abortCheck = (): void => {
if (opts.signal?.aborted) throw new CANCELLED()
}
let sandboxId: string | undefined
try {
abortCheck()
if (!GITHUB_REPO_URL_RE.test(spec.repoUrl)) {
throw new Error(
`repoUrl must be an https GitHub repo URL (https://github.com/owner/repo), got: ${JSON.stringify(spec.repoUrl.slice(0, 100))}`,
)
}
if (opts.env.GITHUB_TOKEN && !GITHUB_TOKEN_RE.test(opts.env.GITHUB_TOKEN)) {
throw new Error(
'GITHUB_TOKEN does not look like a GitHub token (expected ghp_/gho_/ghu_/ghs_/github_pat_ followed by at least 20 more characters) — refusing the run rather than placing a malformed credential into the sandbox.',
)
}
assertProbeSafeHosts(allowOut)
const handle: NetworkCapableSandbox = await provider.create({
projectId: `agent-run-${Date.now().toString(36)}`,
env: {},
labels: { 'molecule.agent-run': '1' },
})
sandboxId = handle.id
if (typeof handle.applyNetwork !== 'function') {
throw new Error('sandbox provider cannot enforce per-run egress; refusing agent run')
}
await handle.applyNetwork(allowOut)
await assertEgress(handle, allowOut)
abortCheck()
const token = opts.env.GITHUB_TOKEN ?? ''
const askpass = token ? buildAskpassHelper(token) : null
const gitEnvPrefix = askpass
? `GIT_ASKPASS=${shellQuote(askpass.path)} GIT_TERMINAL_PROMPT=0 `
: ''
if (askpass) {
const staged = await handle.exec(askpass.stageCommand, { timeout: 5_000 })
if (staged.exitCode !== 0) {
throw new Error('failed to stage the git credential helper inside the sandbox')
}
}
try {
const clone = await handle.exec(
`${gitEnvPrefix}git clone --depth 1 ${shellQuote(spec.repoUrl)} ${REPO_DIR}`,
{ timeout: Math.min(180_000, sandboxBudgetMs) },
)
if (clone.exitCode !== 0) {
throw new Error(
`clone failed: ${redact(clone.stderr || clone.stdout, opts.env).slice(0, 300)}`,
)
}
if (spec.baseBranch) {
const co = await handle.exec(
`cd ${REPO_DIR} && ${gitEnvPrefix}git fetch --depth 1 origin ${shellQuote(spec.baseBranch)} && git checkout ${shellQuote(spec.baseBranch)}`,
{ timeout: 120_000 },
)
if (co.exitCode !== 0) {
throw new Error(
`checkout ${spec.baseBranch} failed: ${redact(co.stderr, opts.env).slice(0, 200)}`,
)
}
}
} finally {
if (askpass) {
await handle
.exec(askpass.cleanupCommand, { timeout: 5_000 })
.catch((_error: unknown) => undefined)
}
}
abortCheck()
const install = await handle.exec(`npm install -g ${shellQuote(this.defaults.cliPackage)}`, {
timeout: Math.min(300_000, sandboxBudgetMs),
})
if (install.exitCode !== 0) {
throw new Error(`claude CLI install failed: ${redactSecrets(install.stderr).slice(0, 300)}`)
}
abortCheck()
await handle.writeFile(PROMPT_PATH, spec.instructions)
const deadlineMs = Math.max(30_000, totalBudgetMs - (Date.now() - started))
const agent = await handle.exec(
`cd ${REPO_DIR} && cat ${PROMPT_PATH} | claude -p --output-format json ${spec.model ? modelFlag(spec.model) : modelFlag(this.defaults.defaultModel)} --dangerously-skip-permissions`,
{
env: {
ANTHROPIC_API_KEY: opts.env.ANTHROPIC_API_KEY ?? '',
...(opts.env.ANTHROPIC_BASE_URL
? { ANTHROPIC_BASE_URL: opts.env.ANTHROPIC_BASE_URL }
: {}),
...(opts.env.ANTHROPIC_AUTH_TOKEN
? { ANTHROPIC_AUTH_TOKEN: opts.env.ANTHROPIC_AUTH_TOKEN }
: {}),
GITHUB_TOKEN: opts.env.GITHUB_TOKEN ?? '',
},
timeout: deadlineMs,
},
)
abortCheck()
const cli = parseClaudeJson(agent.stdout)
log(redact((cli.result ?? agent.stdout).slice(-4000), opts.env))
const stage = await handle.exec(
`cd ${REPO_DIR} && git add -A && (git diff --cached --stat && git diff --cached)`,
{ timeout: 60_000 },
)
const patch = extractPatch(stage.stdout)
return {
exitStatus: cli.is_error || agent.exitCode !== 0 ? 'failed' : 'completed',
patch,
logs: redact(logs.join('\n'), opts.env),
usage: firstUsage(cli),
sandboxId,
computeMs: Date.now() - started,
}
} catch (error) {
const cancelled = error instanceof CANCELLED
const timedOut = !cancelled && isTimeoutError(error)
return {
exitStatus: cancelled ? 'cancelled' : timedOut ? 'timeout' : 'failed',
patch: '',
logs: redact(
`${logs.join('\n')}\n[agent-run] ${cancelled ? 'cancelled by caller' : timedOut ? 'task budget exhausted' : 'run failed'}: ${error instanceof Error ? error.message : String(error)}`,
opts.env,
),
sandboxId,
computeMs: Date.now() - started,
}
} finally {
if (sandboxId) {
await provider.destroy(sandboxId).catch((_error: unknown) => undefined)
}
}
}
}
(Member comments are left out to fit; the full declaration is in node_modules/@molecule/api-agent-runtime-claude-code/dist/provider.d.ts.)
Functions
createProvider(config)
Create a Claude Code agent runtime.
function createProvider(config?: ClaudeCodeRuntimeConfig): AgentRuntimeProvider
config— CLI package, default model, budgets.
Returns: An AgentRuntimeProvider running the Claude Code CLI in ephemeral sandboxes.
Constants
RUNTIME_ALLOWED_HOSTS
Hosts the runtime's own tooling needs, always allowed on top of the spec's.
const RUNTIME_ALLOWED_HOSTS: readonly [
'api.anthropic.com',
'github.com',
'api.github.com',
'codeload.github.com',
'objects.githubusercontent.com',
'registry.npmjs.org',
]
provider
The provider implementation (wire with the agent-run core's setProvider).
const provider: AgentRuntimeProvider
Core Interface
Implements @molecule/api-agent-run interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/api-agent-run'
import { provider } from '@molecule/api-agent-runtime-claude-code'
export function setupAgentRuntimeClaudeCode(): void {
setProvider(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/api-agent-run>=1.0.0@molecule/api-ai-tools>=1.0.0@molecule/api-code-sandbox>=1.0.0
Runtime Dependencies
@molecule/api-agent-run@molecule/api-ai-tools@molecule/api-code-sandbox