agentleFS
fkguofkguo/nullius/skills/claude-cli-runner/SKILL.md

claude-cli-runner

Skill20 starsChanged 22 days agoMITView on GitHub

Run the local `claude` CLI in non-interactive mode (with optional retries/backoff) for arbitrary prompts; supports loading system+user prompts from files and writing output to a target file.

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 2 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill when you need to call Claude from the command line (any task), independent of the downstream workflow (review, drafting, …

    line 8
  2. Very specific:

    Every lane starts with --safe-mode: the operator's own context stays out (see Context sources below).

    line 34
  3. Very specific:

    Use --tool-mode review for the read-only review profile (Read,Glob,Grep).

    line 37
  4. Very specific:

    Not measured: CLAUDE.local.md and the git snapshot (excluded per the CLI documentation).

    line 68
  5. Distinctive:

    Do NOT persist/resume sessions for clean-room review rounds whose independence matters — a resumed round sees the earlier turns.

    line 145
  6. Very specific:

    When using a custom ANTHROPIC_BASE_URL (configured in ~/.claude/settings.json), Claude CLI may fail with:

    line 152
  7. Very specific:

    Use --strict-mcp-config to skip MCP tool loading:

    line 162

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Claude CLI Runner

Use this skill when you need to call Claude from the command line (any task), independent of the downstream workflow (review, drafting, translation, etc.).

If you are already running as Claude, don't use this runner to re-invoke yourself. Keep the call in-host: a native sub-agent if your host exposes one (Claude Code's Agent/Task tool), else inline in your own loop. The claude CLI hop adds latency, a separate auth/session, and context loss for zero gain. This runner is for reaching Claude from a DIFFERENT host (Codex / OpenCode / …) for cross-model work. Pick reasoning effort by task difficulty — quality first, not token thrift.

Preconditions

  • Python 3.10 or newer for cross-process monotonic deadlines and process supervision.
  • claude is installed: command -v claude
  • You are authenticated/configured for Claude Code CLI.

Recommended: runner script (retries + file inputs)

SKILL_DIR="${SKILL_DIR:-$(for r in "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${CODEX_HOME:-$HOME/.codex}" "$HOME/.config/opencode" "$HOME/.kimi-code"; do [ -d "$r/skills/claude-cli-runner" ] && echo "$r/skills/claude-cli-runner" && break; done || true)}"
bash "${SKILL_DIR}/scripts/run_claude.sh" \
  --system-prompt-file /path/to/system.txt \
  --prompt-file /path/to/prompt.txt \
  --out /path/to/output.txt

Notes:

  • The runner always uses --print + --no-session-persistence.
  • Every lane starts with --safe-mode: the operator's own context stays out (see Context sources below).
  • --model is optional. If omitted (or set to default upstream), the runner uses Claude CLI's configured default model.
  • It disables tools by default (--tool-mode none, equivalent to --tools "").
  • Use --tool-mode review for the read-only review profile (Read,Glob,Grep).
  • --tools ... remains available as an explicit advanced override when you need a custom built-in tool list.
  • It enables --strict-mcp-config by default to avoid side effects and MCP schema loading issues (disable with --no-strict-mcp-config).
  • It retries on failures with exponential backoff (useful for transient 5xx/overload). All calls and backoff share an explicit --timeout-secs hard budget when supplied; the default has no hard deadline.
  • --max-retries caps total CLI calls (default 6), including retries. Incomplete/error terminal results, confirmed inactivity, cancellation and budget exhaustion stop without an automatic retry.
  • Deterministic failures (usage/auth/region-ineligibility, quota/billing-cycle and permission errors, exit codes 2/126/127, and similar, detected from the exit code and diagnostics) fail immediately with the diagnostic instead of burning the retry backoff.
  • It feeds the user prompt via stdin (--input-format text + < --prompt-file) to avoid macOS/Linux ARG_MAX limits (fixes Argument list too long with 1–5MB prompt packets).
  • For offline/CI validation, use --dry-run to print the planned invocation without calling Claude (prints only paths + size + sha256; never the full prompt).

Context sources (hermetic by default)

A lane carries only its own request: the system prompt file and the prompt file. Without further flags the Claude CLI would also load the operator's user-level instruction file, CLAUDE.md/CLAUDE.local.md/ AGENTS.md of the working directory and its parents (and of subdirectories once a file there is read), the project's auto memory, installed plugins, hooks and skills. Auto memory and instruction files routinely record earlier conclusions and the author's judgment, so a lane that loads them is not independent of the claim it checks, and their style rules change the lane's language.

  • The runner passes --safe-mode, which loads none of those sources and keeps the operator's login, and sets CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1, which drops the git status snapshot (recent commit subjects of the checkout can state a conclusion). Measured with marker files: user-level CLAUDE.md, CLAUDE.md/AGENTS.md of the working directory and the home directory, a subdirectory CLAUDE.md after a read, and auto memory all reached the lane without the flag and none did with it. --bare would also drop them but refuses the login (it needs an API key); --setting-sources "" drops the instruction files but still loads auto memory; a fresh CLAUDE_CONFIG_DIR loses the login.
  • What still reaches the lane: the CLI's environment block (working directory, platform, model, date), its agent identity line, and the account e-mail address the login attaches. Nothing measured removes the e-mail while keeping the login.
  • Not measured: CLAUDE.local.md and the git snapshot (excluded per the CLI documentation); managed-policy settings and policy-configured hooks, which the CLI says still apply; whether a default model, env block or apiKeyHelper in the operator's settings still applies under --safe-mode. An operator who relies on a settings-only default model or proxy should pass --model explicitly and check the lane.
  • Each invocation writes context_sources.json into its attempt directory (OUT.attempts/run.*/, or TRACE_DIR/run.*/ with --trace-dir): mechanism, excluded sources (measured, and stated by the CLI documentation), admitted sources and what was not measured. Under --live-capture each attempt also writes attempt_N.context_observed.json from the stream's init event. A lane declared hermetic whose init event reports an auto-memory location, or that succeeds without any init event, exits 78 without retry and its answer is moved to OUT.not-hermetic (partial text to OUT.not-hermetic.partial). The observation lists the skills and agents the lane could use; its plugins_installed field lists installed plugins, which the init event names even when none is loaded. The init event does not list loaded CLAUDE.md files; their exclusion rests on the CLI flag, measured with marker files when this default was introduced.
  • --load-operator-context restores the CLI's default loading for interactive-style uses; the records then state hermetic: false. Independent review and derivation lanes must not pass it. Material a lane legitimately needs goes into its request.
  • A CLI version that does not know --safe-mode is expected to reject it as an unknown option, so the call fails instead of running with the operator's context (not tested against an older CLI).

Progress and durable evidence

--live-capture explicitly opts into --output-format stream-json --include-partial-messages --verbose. Raw events and stderr are written from launch under the seat-local OUT.attempts/run.* directory (or --trace-dir DIR). Live capture does not mirror raw events into ambient legacy attempt-log locations. OUT.partial and each attempt's partial answer contain answer text only; private reasoning stays in the raw evidence. Progress JSON contains event kinds, growth counters, timestamps, capture health and process/provider status, never reasoning text. Replays with the same event identity and payload do not advance the counters; distinct event identities with repeated text do advance. Without event identity, identical text deltas are retained as separate observed increments. Heartbeats do not advance the counters. Text is collected by message identity and content-block index, in original order, with snapshots and deltas reconciled without duplication. A snapshot that carries only the block(s) just completed (one snapshot per block, e.g. thinking then text) is aligned to end at the latest block the stream started. Incompatible block types or text refuse completion while retaining prior partial text. OUT contains the complete assistant text, including earlier messages; tool and thinking blocks stay in raw events. Exit 0, a successful terminal record matching the complete text or final text message, and completed text blocks are required. A response the provider's safeguards stopped (stop_reason refusal) never completes, even when the CLI continues once afterwards and exits 0. Partial text never completes a review contract.

--expected-secs (default 1200) is a soft estimate only. --timeout-secs defaults to 0 (no hard budget); any explicit positive value is a hard total cap across attempts and backoff, unaffected by output growth. --inactivity-secs defaults to 0 (disabled). An explicit threshold records a diagnostic with capture health, last advancing event and provider/process status, followed by --inactivity-grace-secs (default 30, positive). Only a healthy observable stream with earlier advancing content can terminate after this grace. Silent reasoning before any advancing event and unknown/degraded capture cannot establish a stall. Activity establishes generation, not correctness or semantic quality.

Without live capture, successful stdout+stderr output bytes are preserved and progress remains unknown. Buffered silence never enables an inactivity kill. Cancellation and hard-budget exhaustion stop local CLI process groups and preserve existing raw/partial evidence; they do not attest remote-job cancellation. Deterministic failure classification reads stderr and typed stream error messages, never answer or thinking text. Cancellation also interrupts retry backoff without waiting for the delay to finish. Capture directories must be excluded from peer packets/workspaces by the caller's existing read-path membrane. Read-only tool access alone cannot enforce filesystem isolation.

Example:

bash /path/to/claude-cli-runner/scripts/run_claude.sh \
  --system-prompt-file /path/to/system.txt --prompt-file /path/to/prompt.txt \
  --out /path/to/review.txt --live-capture --expected-secs 1200 --timeout-secs 5400

Session continuation (OPT-IN; the default stays a clean-room one-shot)

By default the runner passes --no-session-persistence: nothing is recorded, nothing resumable — the right contract for independent reviews. Session features are opt-in (any of them implies persistence; all live-validated):

  • --persist-session — record the conversation so a later invocation can resume it.
  • --session-id-file FILE — also capture the new session id to FILE. Internally switches claude to --output-format json; --out then receives ONLY the final result text (parsed from the JSON envelope), not raw stdout+stderr.
  • --resume-session ID — send this prompt as the NEXT turn of a previously persisted session (claude --resume ID). Chain multi-turn workflows: run 1 with --session-id-file sid.txt, run 2 with --resume-session "$(cat sid.txt)". Non-racy: the id is explicit.

Do NOT persist/resume sessions for clean-room review rounds whose independence matters — a resumed round sees the earlier turns.

Troubleshooting: 400 Error with Custom API Gateway

Symptom

When using a custom ANTHROPIC_BASE_URL (configured in ~/.claude/settings.json), Claude CLI may fail with:

API Error: 400 {"error":{"message":"input_schema does not support oneOf, allOf, or anyOf at the top level"}}

This happens when MCP tools are loaded and their JSON schemas contain top-level oneOf/anyOf/allOf (produced by Zod's z.discriminatedUnion or z.union).

Temporary Workaround

Use --strict-mcp-config to skip MCP tool loading:

echo "test" | claude --print --no-session-persistence --strict-mcp-config

Note: This disables MCP tools entirely. For scientific research workflows, tools/MCP should remain enabled for full functionality.

Root Cause

Some API gateways (e.g., jp.duckcoding.com, openclaudecode.cn) enforce stricter JSON Schema validation than the official Anthropic API. When MCP tools use z.discriminatedUnion('mode', [...]), the resulting JSON Schema has a top-level oneOf, which these gateways reject.

Permanent Fix

Flatten z.discriminatedUnion schemas to plain z.object with z.enum for the discriminator field:

Before (problematic):

const ToolSchema = z.discriminatedUnion('mode', [
  z.object({ mode: z.literal('get'), id: z.string() }),
  z.object({ mode: z.literal('search'), query: z.string() }),
]);

After (compatible):

const ToolSchema = z.object({
  mode: z.enum(['get', 'search']),
  id: z.string().optional(),      // get mode
  query: z.string().optional(),   // search mode
});

Smoke Test

A smoke test ensures no tool has top-level union constructs:

cd /path/to/nullius/packages/hep-mcp
npx vitest run tests/smoke-no-toplevel-union.test.ts

Affected MCP Projects

The following MCP projects had top-level-union schema fixes (2026-01-28):

  • hep-mcp / @nullius/hep-mcp: inspire_literature and related retained INSPIRE surfaces
  • zotero-mcp: zotero_local

Historical workflow-like tool names may since have been pruned. Use the current tool catalog for live tool names rather than this historical note.

Runner dry-run

SKILL_DIR="${SKILL_DIR:-$(for r in "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "${CODEX_HOME:-$HOME/.codex}" "$HOME/.config/opencode" "$HOME/.kimi-code"; do [ -d "$r/skills/claude-cli-runner" ] && echo "$r/skills/claude-cli-runner" && break; done || true)}"
bash "${SKILL_DIR}/scripts/run_claude.sh" \
  --system-prompt-file /path/to/system.txt \
  --prompt-file /path/to/prompt.txt \
  --out /path/to/output.txt \
  --dry-run

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.