Day 22 of 52 · about 20 min · Automation
Run Claude Code from a script
Once a task works in a session, a script can run it the same way with nobody at the keyboard. That is how Claude gets into build scripts and CI, and it starts with one flag: -p.
You’ll finish with
- One-off answers with
claude -p, including piped input - Runs that may use only the tools you list
- A Node script that reads Claude’s answer as JSON and exits 0 or 1
You’ll need
- Claude Code installed and logged in (Day 1)
- The practice project from the Day 22 kit, or your own project. Every command here works the same in Bash, Zsh and PowerShell.
Before you start
Tell us about your setup and the steps change to fit it. Your answers stay in this browser, and you can change them at any time.
Which system are you on?
Commands differ between systems. Pick the one you’ll run Claude Code on.
The steps
One step at a time. When a step’s check matches, tick it and the next one opens.
Step 1 of 4: Ask one question
In the project folder, pass a question after `claude -p`.TerminalRun: claude -p "Which command runs this project's tests?"
What happens: Claude reads the project, prints its answer and exits.
One prompt in, one answer out, and no session to close. Check it worked
The answer names the test command (
npm testin the practice project).echo $?(PowerShell:$LASTEXITCODE) prints0.Step 2 of 4: Pipe another command’s output in
Send a command’s output to Claude on stdin and ask about it.claude -preads stdin, so any command’s output can go to Claude. Here it is a quote; in your own project, pipe a log orgit diff.TerminalRun: node src/cli.js design:10 dev:24 --discount 10 | claude -p "Check the maths in this quote. Reply OK or name the wrong line."
The quote travels on stdin; the instruction travels after -p.Check it worked
Claude replies OK. The sample quote is correct.
Step 3 of 4: Pre-approve one tool and get JSON back
List the tools Claude may use without asking, and ask for JSON output.With
-p, nobody is there to answer a permission prompt, so anything that would prompt is refused.--allowedToolsapproves exactly what the task needs.TerminalRun: claude -p "Run the tests and say how many passed" --allowedTools "Bash(npm test *)" --output-format json
What happens: One JSON object: the answer in
result, plussession_idand an estimated cost intotal_cost_usd.Bash(npm test *)approvesnpm test, with or without arguments, and nothing more.Check it worked
The JSON’s
resultfield says how many tests passed.Step 4 of 4: Let a script read the answer
Run the kit script, which asks for JSON in a fixed shape.The kit’s script pipes the quote to
claude -pwith--json-schema, then readsokandproblemsfrom thestructured_outputfield.scripts/check-quote.mjsJavaScript/** * Day 22: run Claude Code from a script. * * node scripts/check-quote.mjs * * Prints the sample quote with src/cli.js, pipes it to `claude -p` and asks * Claude to check the arithmetic. `--output-format json` with `--json-schema` * makes Claude Code return the answer in the `structured_output` field, so * this script reads two fields instead of parsing prose. * * Exit codes: 0 the quote checks out, 1 Claude found a problem, 2 Claude * could not be run or gave no answer in the expected shape. * * Flags and fields: https://code.claude.com/docs/en/headless (checked * 9 October 2026). * * On Windows, Node starts `claude.exe` from the native installer. An npm * install provides `claude.cmd`, which Node can't start without a shell. */ import { spawnSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; const ROOT = fileURLToPath(new URL('..', import.meta.url)); const SAMPLE = ['src/cli.js', 'design:10', 'dev:24', '--discount', '10']; const SCHEMA = JSON.stringify({ type: 'object', properties: { ok: { type: 'boolean' }, problems: { type: 'array', items: { type: 'string' } }, }, required: ['ok', 'problems'], }); const PROMPT = [ 'The text on stdin is a price quote.', 'Each line is hours times an hourly rate.', 'The discount is a percentage of the subtotal.', 'VAT is a percentage of the amount after the discount.', 'Check every line and every total.', 'Set ok to true only if all of them are right.', 'List each wrong line in problems, with the value it should have.', ].join(' '); function fail(message) { console.error(message); process.exit(2); } const quote = spawnSync(process.execPath, SAMPLE, { cwd: ROOT, encoding: 'utf8' }); if (quote.status !== 0) fail(`Could not print the sample quote:\n${quote.stderr}`); const run = spawnSync('claude', ['-p', PROMPT, '--output-format', 'json', '--json-schema', SCHEMA], { cwd: ROOT, input: quote.stdout, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, }); if (run.error) fail(`Could not start claude (${run.error.code}). Check that \`claude --version\` works in this terminal.`); if (run.status !== 0) fail(`claude -p exited with code ${run.status}:\n${(run.stdout + run.stderr).trim()}`); let answer; try { answer = JSON.parse(run.stdout).structured_output; } catch { fail(`claude -p did not print JSON:\n${run.stdout.trim()}`); } if (!answer || typeof answer.ok !== 'boolean' || !Array.isArray(answer.problems)) { fail(`No answer in the expected shape:\n${run.stdout.trim()}`); } if (answer.ok) { console.log('Quote checked: no problems found.'); } else { console.log('Quote checked: Claude found problems.'); for (const problem of answer.problems) console.log(`- ${problem}`); process.exitCode = 1; }TerminalRun: node scripts/check-quote.mjs
Output: Quote checked: no problems found.Exit code 0 means the quote checks out, 1 means Claude found a problem, 2 means Claude couldn’t run. Check it worked
The script prints
Quote checked: no problems found.and exits with0.If it didn’t work
The script says it could not start claude
Check that
claude --versionworks in the same terminal. On Windows, Node can start the native installer’sclaude.exebut not npm’sclaude.cmd: use the Day 1 installer.The result says you are not logged in
/logindoesn’t work with-p. Startclaudeonce, run/login, then try again.
Finish · 4 of 4 steps to go
Claude runs without you at the keyboard
--bare so local hooks, skills and CLAUDE.md can’t change the run.What you’ll have checked
claude -panswers and exits with code 0- Piped output reaches Claude
node scripts/check-quote.mjsprints its result and exits 0
Lesson updates by email
Get lesson updates by email
Leave your email and we’ll tell you when we add or update a lesson.
Claude Code for your team
Want Claude Code set up across your team?
HorizonLux sets up and repairs Claude Code for teams: CLAUDE.md rules, hooks that enforce them and reviewed MCP servers. Book a free call to talk it through.
Related
Questions
Questions about Day 22
Can --bare use my Claude subscription?
No. Bare mode never reads your subscription login, so set `ANTHROPIC_API_KEY` with a key from the Claude Console first.
Sources
Checked against the official Claude Code docs on .
Independent resource, not affiliated with Anthropic. HorizonLux wrote this lesson; Claude Code is made by Anthropic.