Runs the Claude Code CLI (claude -p) once in workdir and returns its
answer as an R object that matches a JSON Schema. The agent can read the
files under workdir but cannot change them, run commands or reach the
web.
Arguments
- prompt
The task, as a single string.
- schema
A JSON Schema, as a single JSON string, that the answer must match. Write it as JSON text, not as an R list: converting a list with
jsonlite::toJSON(auto_unbox = TRUE)turns one-element arrays such asrequiredinto scalars and silently breaks the schema.- workdir
Path of an existing directory the agent works in. Its files are read in place, not copied.
- timeout
Wall-clock limit for the whole run, in seconds. When it runs out, the CLI and all its child processes are killed.
Value
An object of class fmisc_agent_result, a list with:
valueThe structured output, parsed by
jsonlite::fromJSON()with default simplification (arrays of objects become data frames).cost_usdThe CLI's estimated cost. Under a subscription this is not what you pay.
num_turnsNumber of agent turns.
modelThe model(s) the CLI actually used.
session_idFor correlation only; the session cannot be resumed.
permission_denialsTool calls the CLI refused, which show when the agent tried to step outside read-only mode.
rawThe full parsed CLI result, for debugging. Not part of the stable contract.
Authentication and billing
agent_run() uses whatever login the claude CLI already has, meant to
be the owner's Claude subscription (OAuth). It never reads, stores or
passes tokens. These variables are removed from the child process's
environment, so nothing set in the R session can switch the run to API
billing, a gateway or a cloud provider: ANTHROPIC_API_KEY,
ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL, ANTHROPIC_CUSTOM_HEADERS,
CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX,
CLAUDE_CODE_USE_FOUNDRY, CLAUDE_CODE_USE_MANTLE and
CLAUDE_CODE_USE_ANTHROPIC_AWS. CLAUDE_CODE_OAUTH_TOKEN (a subscription
token from claude setup-token) passes through. cost_usd is the CLI's
client-side estimate, not a bill. Use it for occasional, attended calls;
it is not made for batch runs over many rows.
Isolation
The CLI runs with --safe-mode --restricted --strict-mcp-config, so
CLAUDE.md, skills, plugins, hooks, MCP servers and settings files are not
loaded. Only the Read, Glob and Grep tools are available, and any
call that would need a permission prompt is denied (dontAsk,
--permission-prompts none). The model (sonnet), the turn cap (20) and
the budget cap (USD 1) are fixed. The prompt goes on stdin, never on the
command line, and no session transcript is kept.
A fixed list of Read deny rules blocks common secret files at any depth:
.env*, .Renviron*, .netrc, .git-credentials, *.pem, *.key,
SSH keys (id_rsa*, id_dsa*, id_ecdsa*, id_ed25519*) and .ssh/.
Claude Code applies these best-effort to Glob and Grep as well. This is
defense in depth, not a sandbox: the agent can read every other file
in workdir and send it to the model, including secrets under other
names. Point workdir at a directory that holds only what the agent
should see. Managed (policy) settings still apply.
Needs Claude Code CLI 2.1.259 or later on PATH. Not supported on Windows yet.
Errors
Every error inherits from fmisc_agent_error:
fmisc_agent_invalid_argumentA bad argument, including a
schemathat is not valid JSON. Raised before the CLI starts.fmisc_agent_missing_packageprocessx or jsonlite is not installed.
fmisc_agent_cli_missingclaudeis not on PATH.fmisc_agent_timeoutThe run hit
timeout. Fields:timeout,stderr.fmisc_agent_failedThe CLI exited non-zero, reported
is_error, or printed output that is not JSON. Fields:exit_status,subtype(such as"error_max_turns"),stderr.fmisc_agent_no_outputThe run succeeded but returned no structured output, typically a refusal. Fields:
subtype,permission_denials.
Messages never contain the prompt, the model's text or file contents.
Examples
if (FALSE) { # \dontrun{
schema <- paste0(
'{"type": "object", "required": ["functions"], "properties": ',
'{"functions": {"type": "array", "items": {"type": "string"}}}}'
)
res <- agent_run(
"Which exported functions in this package have no examples?",
schema = schema,
workdir = "."
)
res$value$functions
res
} # }