There is a pattern that runs on a lot of developer machines around six in the morning. A cron entry, three lines of bash, and a single Claude invocation. The terminal that fires it is empty. There is no one watching the output. The job runs, writes to a logfile, and exits. By the time the developer wakes up, there is a paragraph waiting for them: yesterday's commits reviewed, the risky one flagged, the boring ones acknowledged. Total cost: a few cents of compute and about a minute of clock time.
This is headless Claude, and most people who have a Claude subscription have never actually used it. They open the desktop app, they open a terminal, they have a conversation. They wait for replies and they make decisions. The whole shape of the interaction assumes a human in the loop. Then they close the laptop and go to bed, and Claude goes idle until the next time someone opens an app.
But there is another mode. It does not show up in the marketing. It barely shows up in the docs unless you go looking. It is a single command-line flag, and it changes what Claude is good for. The flag is dash p. Sometimes written as minus minus print. Same thing.
And once you have internalized what dash p does, your shell starts to feel different. Tasks that used to need a session now need a single line. Things that you would have written a script for now take an alias. The keyboard becomes a place where Claude is a verb, not a destination.
So this is a tour of that shape. What dash p actually is, how cron jobs that use it actually work, what aliases are worth keeping, what gotchas you will hit, and how to think about the spaces around the Mac, the VPS, and the small server in your living room.
Run claude with no flag and you get the interactive experience. The terminal interface, the prompt, the questions before tool calls. Claude waits for you. You wait for Claude. There is a rhythm to it.
Run claude with dash p and a prompt as the next argument, and you get something else. Claude reads the prompt, does whatever the prompt requires, prints the result to standard output, and exits. There is no terminal interface. There is no waiting for human input. The process runs to completion or it dies.
The official phrasing for this used to be headless mode. Anthropic now calls it running Claude Code programmatically, which is more accurate but less evocative. The dash p flag and all the related options work the same as they always did.
Here is the part that takes a minute to internalize. When Claude is interactive, it asks before it does things. Before reading a file, before writing a file, before running a bash command, it pops up a prompt asking if this is okay. In headless mode there is no asking. There is no human to answer the question. Either you have pre-authorized the tools you trust, or Claude freezes waiting for an answer that will never come.
So a working headless invocation usually has three things. The prompt, what you want done. The allowed tools list, scoped narrow. And a budget, either turns or dollars or both. Without those guardrails, you are handing a sharp instrument to a process you cannot see.
There is also a flag worth knowing: dash dash bare. Bare mode tells Claude to skip auto-discovery. It does not load your skills, your hooks, your project's configuration files, your MCP servers. It runs with only the flags you pass it. This is what you want for continuous integration jobs and reproducible scripts, where the same command should produce the same result regardless of which machine fires it. It is also what you want when speed matters, because skipping auto-discovery cuts startup time noticeably.
One more thing about bare mode. It skips the OAuth keychain read entirely. So if you are on a Mac and authenticated through your subscription, bare mode ignores that and demands an API key in the environment. Sometimes that is what you want. Sometimes it is a surprise.
This is where things get fiddly, especially on Mac.
If you are an interactive Claude Code user on a personal machine, you log in once with your subscription and it just works. The OAuth tokens go in the system keychain, the CLI knows how to find them, the world is good.
The moment you start running claude from cron, things change. Cron processes do not inherit your interactive shell environment. They have their own minimal environment, and on Mac they have spotty access to the keychain. Some commands work, some do not, and the failure mode looks like an authentication error at six in the morning that you do not see until eight.
The practical pattern that works: do not trust the keychain in cron. Either explicitly set the ANTHROPIC API KEY environment variable in the cron entry itself, or have the entry source a file that contains it. The second one is cleaner because you do not put your key in your crontab where anyone with read access can see it.
A reasonable cron entry looks like this in concept. At six every day, source the env file, change to the project directory, run claude with dash p and the prompt, redirect output to a log, redirect errors to the same log. The whole line is one shell invocation, and the env file is owned by your user and locked down to read by owner only.
If you are using your subscription rather than an API key, the story is murkier. There are workarounds, like using a long-lived OAuth token that you set up explicitly, but they are fragile. They expire. They have to be refreshed. Anthropic has been clear that the subscription auth path is for ordinary individual use, which a cron job at three in the morning arguably is not. Use an API key for cron. Save the subscription for the work where you are at the keyboard.
There are four distinct mechanisms for getting Claude to run on a schedule, and they do different things.
The first is slash loop, a CLI command you run inside a Claude Code session. It tells the current session to repeat something at an interval, watching for changes, reporting back when something happens. It is ephemeral. The moment you close the terminal, the loop dies. Use it when you want Claude to keep an eye on something while you keep working in the same session.
The second is desktop scheduled tasks, available through the Claude Code desktop app. You schedule a task with a prompt and a frequency, and the app fires a fresh Claude Code session at the right time. The session has access to your local files, your uncommitted changes, your MCP servers. It survives restarts. The catch: it needs your computer to be awake. If your laptop is asleep at nine in the morning, the task is skipped. There is a catch-up mechanism that runs missed tasks once when the machine wakes, but only once.
The third is cloud tasks, scheduled with slash schedule. These run on Anthropic's infrastructure. They get a fresh clone of your repo, no access to uncommitted changes, no access to your local network. But they run regardless of whether your laptop is on. If you need a task to definitely happen at nine every morning, this is where it goes.
The fourth is system cron with claude dash p. This is the oldest school. Cron is a Unix scheduler that has been around since the early eighties. It does not know anything about Claude. It runs commands. You give it a command line that includes claude dash p, and it executes that command line on the schedule you specify. No Anthropic involvement. No magic. Just a process firing at a time.
Each of these has a place. For watching something while you work: slash loop. For things that should happen if your computer is on: desktop tasks. For things that should always happen: cloud tasks. For things that need full control of the environment, including private network access, custom binaries, and arbitrary shell wrapping: system cron. The four are not redundant. They cover different operating contexts.
This is the part that changes how the shell feels. The aliases.
Each alias represents a single-purpose verb. You invoke the verb with a short description of what you want, and Claude does it within the constraints the alias defines. The trick is to make the constraints tight enough that the alias is safe to fire reflexively, and loose enough that it does something useful.
Start with the safest one. An audit alias. Read-only tools, low turn limit, fast startup with bare mode. The alias becomes claudit, or aud, or whatever short name fits. You can fire it whenever a question comes up. Audit, what is the largest file in this repo. Audit, find any TODO comments older than three months. Audit, which directories have not been touched since last quarter. The function does the read-only investigation, returns a paragraph, and exits. Cost is pennies. Wall time is seconds.
Add a fix alias. This one writes. Tools include read, edit, and a scoped bash for the package manager and git. Higher turn limit, maybe twenty. A budget cap of one dollar so a runaway loop does not bankrupt your week. Use it when you have something specific you want fixed. Fix, run the tests and fix anything that breaks. Fix, the linter is angry, make it stop. Fix, the imports in this file are out of order. The fix alias takes over for the kind of small repetitive cleanup work that you would otherwise have done by hand or by writing a one-off script.
Add an ask. The shortest of all. A function that takes everything you typed after the function name as the prompt, runs claude dash p with bare mode, prints the answer, and exits. No tools, no edits, just a question and an answer. Ask, what is the syntax for in-place editing in sed on Mac. Ask, what is the difference between psycopg two and psycopg three. Ask, why is my docker container exiting immediately. The answers are general, the cost is tiny, and the friction is gone. After a few weeks, ask is the most-used Claude command you have.
Add a commit message generator. This one is fancier. It takes the staged git diff, pipes it into claude dash p with a prompt that asks for a conventional commit message based on the diff, and prints the result. You can pipe the result straight into git commit dash m via command substitution if you trust it. Some people prefer to pipe to the clipboard and review before committing. Either works. After a week of using it, you stop writing commit messages by hand. The quality is consistent and the format is right.
Add a triage. A function that takes a logfile path, cats it into claude dash p, asks for a summary of errors grouped by frequency. Useful when something has gone wrong overnight on your VPS and you wake up to two hundred lines of stack traces. Triage, last night's parpod log. Triage, the docker compose output from this morning's deploy. The function does what you would have done with grep and sort and uniq, but it understands the actual structure of the errors rather than just counting strings.
Add a compose. A function that takes a recipient and a topic, looks at recent context, and drafts a message. This works particularly well when you have a project directory open and you need to write to a client about it. The compose alias reads the recent files, picks up the relevant context, and writes a draft. You edit and send. The amount of, what do I say in this email, friction the alias removes is genuinely surprising.
The pattern is consistent. Each alias has a narrow purpose. Each one constrains tools and budget. Each one becomes a verb you can fire from anywhere, including from inside other scripts. Some of these become the kind of thing you reach for ten times a day. Some of them you use once a month. Both are fine.
The reason this changes the shell is not any single alias. It is the cumulative effect. Once five or six of these are in place, you stop reaching for the chat app for small things. The chat is for conversations. The shell is for tasks. Both are talking to the same Claude.
A short detour into something that matters when you start composing aliases into pipelines.
By default, claude dash p prints human-readable text to standard output. That is fine for an alias whose only consumer is your eyes. It is not fine for an alias that feeds another script.
The dash dash output format flag handles this. Set it to json and you get a structured response with messages, tool calls, token counts, the final answer, the cost. You can pipe that into a JSON processor like jq, pull out the field you want, and feed it onward. Set it to stream-json and you get the same structure but emitted progressively as Claude works, useful when you want to watch a long-running task in real time.
The streaming format is also what you want when building a control plane on top of headless Claude. Each message arrives as soon as it exists. Your supervisor process can react in real time, kill the run if it goes wrong, surface progress to a user interface. Without the streaming format, the supervisor has to wait until the whole run finishes before it knows anything.
For shell aliases, the simple pattern is plain text out, sometimes piped through head or grep to extract a specific line. For anything more structured, switch to json and parse properly. Do not try to grep human-readable output for control flow. That way lies tears.
Now here is where it gets interesting for a stack that already has a Director MCP server in it.
Claude Code, when you run it without bare mode, picks up the MCP servers configured in your settings. So a headless Claude has the same Director powers as an interactive Claude. The full set of tools, the full state, the full reach.
This composes in ways that take a while to appreciate.
A cron job, fired at the end of the day, runs claude dash p with a prompt like: review what I worked on today, write a session summary, and append it to the project I was in. Claude pulls the location history, looks at the recent files, infers the project, calls the Director append session tool, and exits. The next morning, yesterday's work is already documented, ready to be the starting point for today.
A different cron, fired in the morning, runs claude dash p with a prompt: check the local data store for any anomalies in the BMW data overnight, and if you see anything weird, send a mail through Director. Most mornings nothing happens, the cron returns empty, the day continues. But on a morning when the car has a charging fault or the GPS dropped at a strange location, there is a mail in the inbox before breakfast.
A third cron, fired weekly, runs claude dash p with a prompt: scan the Director ingest stream from the past week for anything that looks like a promising experiment, and write a short list of what to follow up on. It is a research assistant that does not take a paycheck, does not get tired, and does not have an opinion about which experiments matter except the ones you have already shown it you care about.
The composition pattern here is three layers. Cron is the trigger. Claude dash p is the executor. Director MCP is the persistent state and the routing. Each layer is small. Each layer composes cleanly with the others. You can swap any one of them without breaking the others. Cron could become a webhook. Claude dash p could become an Agent SDK call with an API key. Director could become a Postgres query directly. The shape stays.
The reason this is interesting is that none of these three layers individually is doing anything special. Cron has been around forever. Claude dash p is one CLI flag. Director MCP is a small Python service exposing a handful of tools over a standard protocol. The interesting thing is the composition. The whole is genuinely more than the parts.
Headless mode is single shot. One process, one prompt, one result. Anything that needs to live across multiple invocations, or anything that needs to recover from a crash, or anything that involves a human stepping in mid-flow: not what dash p is for.
For long-running unattended work, you need a supervisor. There are three tiers, and most people start in tier one and graduate as their needs grow.
Tier one is tmux, the classic terminal multiplexer. You start a tmux session, fire your claude command inside it, detach. The session keeps running after you close the terminal, after you close the laptop, after you log out from ssh. You can come back later, attach to the session, see what happened. For overnight refactor jobs and exploratory experiments that take hours, tmux is the lowest friction option you have. The tradeoff is that tmux does not restart anything if it crashes. If your claude run dies for any reason, the tmux session is just an empty pane the next time you look.
Tier two is systemd, on Linux. A real service. You write a unit file that says: run this claude command, restart it if it crashes, log to journald with a sane name. Now you have a thing that survives reboots, recovers from failures, and can be started and stopped with system commands. For genuinely persistent automation on a VPS, systemd is the right answer. The tradeoff is the setup cost. You have to write the unit file, you have to think about restart policies, you have to handle log rotation. For one or two services it is fine. For ten, it starts to feel like infrastructure work that distracts from the actual problem.
Tier three is a control plane. There are a few of these now. Background Claude is one. amux is another. They handle fleets of headless agents, with dashboards, cost tracking, and parallelism management. If you are running enough automated Claude that a single tmux session is not going to cut it, this is the next step. Most people do not get there. Most people do not need to.
The general advice: start with tmux for one-off long runs, move to systemd when you have a service that needs to be reliable, only think about a control plane when you can name three or more independent agents you are running and the operational overhead is starting to bite.
Let us talk about the gotchas, because there are a few that bite everyone.
The first one: cron does not have your PATH. You SSH in, type claude version, get a clean response, write a cron entry that says claude dash p prompt, schedule it for tomorrow morning. Tomorrow morning the cron fails with command not found. The reason is that cron uses a minimal PATH that does not include the npm global directory where claude lives. The fix is to either use the full path to the claude binary in your cron entry, or to set PATH at the top of your crontab, or to wrap the call in a shell that sources your profile.
The second: Claude waits for input it will never get. You wrote a cron job that runs a fix task. You forgot to add dash dash allowed tools, or you misspelled one of them. Claude tries to call a tool, the permission prompt fires, there is no human to answer it, and the process sits forever. Or until the cron timeout kills it. Either way, no work gets done. The fix is to always set allowed tools explicitly, and to add dash dash max turns and dash dash max budget caps so that even if Claude does the wrong thing, it does it cheaply and briefly.
The third: the budget runs out and the work is half done. You set a one-dollar budget, Claude burns through ninety cents fixing test failures, then hits the cap right as it is about to commit. The output is a half-finished change in your working directory. The fix is partly a budget tuning question, partly a prompt design question. Prompts that say make all the changes needed in one pass, then commit, are more bounded than prompts that imply iterate until everything passes. The first one terminates. The second one can spin.
The fourth: rate limits. Even with a Max subscription, even with an API key, you can hit rate limits if you fire too many parallel headless jobs. The error usually shows up as a four-twenty-nine response code. The fix is partly to spread the work out in time, partly to use slower models when possible. Sonnet does ninety percent of what Opus does at a fraction of the rate-limit cost.
The fifth: the cron actually worked, but you cannot tell. You wrote a cron that does something useful. It fires, it produces output. But you redirected stdout and stderr to the same log file, which is now huge, and you cannot tell from looking whether the cron is doing what you wanted or whether it has been silently failing for three weeks. The fix is to think about logging up front. Have the cron write a one-line summary of what it did to a separate file. Rotate the verbose log. Treat the log files as part of the design, not an afterthought.
None of these are deep bugs. They are all configuration mistakes. They will all happen to you at least once.
A useful frame: every machine in your stack has a personality. The questions you give it should match.
The Mac is your personal computer. It has your tools, your aliases, your local network, your Keychain, your subscription. It powers down at night. It is online when you are working. It is the natural home for things that fit your work day. The morning commit review. The afternoon log triage. The end-of-day session digest. Anything where you want Claude to operate in your private context, with your tools, the Mac is right.
The VPS is your always-on infrastructure. It runs your web services. It speaks to the internet on its own behalf. It does not have a Keychain, it has API keys in environment variables. It runs whether you are awake or asleep. The natural home for things that need to happen regardless of your presence: scheduled tasks for users on your sites, monitoring of public services, periodic data collection. The cost is real, because every API call there is on the meter, but for the work that lives there, that is the right model.
The Pi at home, the Raspberry Pi five sixteen gig that handles home automation and the BMW MQTT bridge and SMS reminders. The Pi is the in-between. Always on like the VPS, but on your local network like the Mac. Low power, low capacity, but persistent. The natural home for things that need to react to local events: a temperature sensor reading, an MQTT message from the car, a doorbell ring. If you wanted, you could run claude dash p on the Pi, with an API key, in response to events. Most of the time you do not need it. But the option is there.
The decision tree that emerges. Does this need to run when I am not at the Mac. If no, the Mac is cheaper and has access to everything. If yes, does it need access to my local network or local hardware. If yes, the Pi. If no, the VPS. That tree covers most cases without fuss.
Step back for a moment.
A cron job is a sentence. It has a time, a place, and an action. The time is the schedule expression, those five fields with asterisks and numbers. The place is the user account, the working directory, the environment. The action is the command line.
Most cron jobs are short. One line of bash, maybe two. They run, they exit, they go back to sleep. The terseness is part of the appeal. Crons that grow to fifty lines of shell are crons that should have become Python scripts.
But a cron job with claude dash p in it has a different shape. The action is no longer a fixed sequence of operations. The action is a prompt, an instruction in English, that gets resolved into a sequence of operations by Claude at runtime. The cron is still a sentence, but the verb is now flexible.
This is unsettling at first. For decades, cron jobs were predictable. The same schedule, the same command, the same effect, day after day. A claude cron is not quite that. The effect depends on what Claude finds when it runs, what files exist, what the state of the world is. Two runs of the same cron, with the same prompt, can do different things on different days.
That is a feature when you want it. The morning commit review only flags a commit if there is a commit to flag. The log triage only sends a mail if there is something worth mailing about. The session summary only appends if there was a session.
It is a bug when you do not want it. If you need exactly the same operation every time, you do not want claude dash p in your cron. You want a script. Do not use Claude where determinism matters more than judgment.
The aesthetic question is when to use which. The answer is roughly: if the work you would do by hand is the same every day, write a script. If the work you would do by hand varies based on context, claude dash p is interesting. The friction of writing a prompt is lower than the friction of writing handling logic for every edge case you can imagine.
Here is the shift, and it takes a while to see it clearly.
Before headless Claude, most people's relationship with AI was conversational. You opened a chat, you typed a thing, you read the response, you closed the chat. The interaction was bounded by your attention. If you were not paying attention, nothing happened.
Headless changes the bound. The cron is paying attention. The shell alias is paying attention. The MCP-powered automation is paying attention. The chat is still there, still useful, but it is no longer the only point of contact. Claude moves from a destination you visit to a layer in your stack.
The benefit compounds. Every alias you add reduces the friction of using Claude for some category of work. Every cron you write subtracts a recurring task from your future self. Every MCP integration extends what either of those can reach. The investment is one-time. The payoff is permanent until something changes upstream.
For a stack that already has Director MCP in it, the shape that emerges is something like this. The Mac handles the daily rhythm: morning review, afternoon triage, end-of-day summary. Director MCP is the persistent memory across all of it. The VPS handles public-facing concerns with API keys. The Pi handles local hardware. The phone is for chat, when chat is the right shape. The shell is for verbs, when verbs are the right shape. Claude is in all of them.
The version of this that does not work is the one where every interaction goes through chat, and you find yourself typing the same kinds of prompts over and over. That is a sign there is an alias missing. Or a cron. Or both. The instinct that says I should automate this is usually right, and the activation energy to actually write the alias is lower than you think. Five minutes, one shell function, done.
The version that does work is the one where you cannot quite tell, anymore, where Claude ends and your tools begin. The boundary blurs. Things just happen, and the things that should happen at six in the morning are already done by the time you wake up. The morning routine becomes: open the laptop, see what has been done, react to the small handful of things that need a human eye, get on with the day.
That is the workshop behind the dash p flag. It is not a single feature. It is a stance: that AI assistance does not have to be a place you go. It can be a thing your shell does, a thing your cron does, a thing your daemons do. Once you start treating it that way, everything else follows.