I open last night's scheduled-run log first thing in the morning. One morning the file ended after its header line. The run had not crashed. It had been waiting.
When I traced it back, nothing was misconfigured. I had put the work in the wrong place. A sequence that behaved perfectly while I sat in front of it had been moved, unchanged, into a slot where nobody was watching the screen.
Cowork and Claude Code run the same Claude. So "which one is better" turned out to be nearly useless as a question for me. What helped was narrower: can a human approval interrupt this step, or does an approval here end the run?
Approvals that protect you, and approvals that stall you
An approval prompt is a safety device, and on the attended side it does exactly what it promises. When something reaches for a file I did not intend to touch, it stops and asks.
The same device behaves differently at three in the morning. It does not fail — it waits. Because waiting is not an error, nothing is raised, nothing is reported, and no exception lands in the log. You find out later, when a morning log ends after its header.
This is not only my setup. Adding one ordinary deny rule can make a compound Bash command that includes cd and a relative read ask for approval on every single run (claude-code Issue #91650). The check lands on how the command is assembled rather than on the risky operation itself. If you schedule anything, that behaviour is worth knowing about before it finds you.
The work I pulled back off the unattended side
As an indie developer I have run a set of wallpaper apps for a long time, and preparing assets before a release follows a fixed sequence. I built that sequence interactively, it worked, and — carried along by that — I moved it straight into a scheduled task.
It did not go well. File reads and writes were going through dedicated editing tools, so every run asked for permission and stopped there. On the attended side that prompt costs one keystroke. On the unattended side it is the end of the run.
Looking back, what I moved was not the sequence. It was a habit that quietly assumed someone was sitting there. So I redrew the line.
Nothing on the unattended side is allowed to ask a human anything. If a step genuinely needs a decision, I cut the job just before that step and hand the rest to the attended side.
The rewrite itself was unglamorous: file work moved to shell cat and sed. There is even a report that auto mode's internal guidance suggests sed and short heredocs rather than the dedicated editing tools (Issue #88041), which points the same way. My reason is not speed, though. It is one fewer place where an approval can appear.
The rule I sort by now
I no longer line up feature tables. I say what kind of work it is in one sentence, and that sentence decides where it lives.
| Kind of work | Where it lives | Why |
|---|---|---|
| Runs at the same time in the same shape every day (asset exports, rolling summaries) | Cowork scheduled task | It can be written approval-free from the start |
| Needs a human call partway (is this safe to publish, safe to delete) | Attended session (Cowork or Claude Code) | The approval does its real job here |
| The steps are not settled yet | Claude Code | I can watch it fail and fix it on the spot |
| Touches a repository, diff by diff | Claude Code | I want to read the diff before deciding to stop |
| Repairing a connection to an external service | Attended session | Re-auth and status checks need a screen |
| Right after an OS or app update | Neither — check by hand first | Environment changes sit outside the automation |
The last two rows were added later. Connection repair is available in the CLI but does not reach the desktop surface (Issue #54136), and after one Windows cumulative update, host folder shares reportedly stopped mounting altogether (Issue #92984). I have not hit either one myself. Both live in a layer automation cannot repair, which is why the morning after an update I open a folder by hand before I read any task output.
If the underlying question — watch it work, or let it run — is the part you are weighing, I came at it from a different angle in Claude in Chrome vs. Cowork: Watch It Work, or Let It Run.
What I keep in the unattended scripts
Once the sorting was settled, the unattended scripts got three promises: create no approval surface, confirm the write by something other than a return value, and always leave one line at the end.
#!/usr/bin/env bash
set -u
OUT="$HOME/reports/$(date +%Y-%m-%d)_summary.txt"
mkdir -p "$(dirname "$OUT")"
# Write through the shell, not a dedicated tool, so no approval surface appears
cat <<'EOF' > "$OUT"
# Daily inventory
status: pending
EOF
# Confirm by the artifact on disk, not by the return value
if [ -s "$OUT" ]; then
echo "OK wrote $(wc -c < "$OUT") bytes -> $OUT"
else
echo "FAILED empty output -> $OUT" >&2
exit 1
fiThe expected output is a single line such as OK wrote 34 bytes -> /Users/you/reports/2026-09-18_summary.txt.
Three notes on why it is written this way. The heredoc delimiter is quoted as <<'EOF' so that a $ or a backtick inside the body is not expanded. Drop the quotes and the log you thought you wrote comes out empty, or truncated at the first surprise.
The -s check is there because "the call succeeded" and "the bytes exist" are different claims. A write tool returning success while no file appears has been reported (Issue #81538). A return value only tells you the call was reachable.
The final line exists because an empty log cannot distinguish "it never ran" from "it ran and found nothing." Without that distinction, whoever reads it in the morning starts the investigation from zero every time.
One smaller habit goes with it: I do not chain compound commands onto a single line.
# Approval checks can land on the leading cd, so keep the steps separate
cd "$WORK" || exit 1
git add content/
git commit -m "update: daily inventory"The chained version is shorter, I will grant that. I keep them split because when it stops, the log tells me which line stopped it. On the unattended side I would rather be able to re-read the run than save a line. For how the scheduled tasks themselves are put together, I wrote that up in Running Cowork Scheduled Tasks in Practice.
Keeping the attended side light with narrow permissions
Driving approvals to zero on the unattended side pushes more of them onto the attended side. Let that get heavy and you are the one who stalls. Rather than opening permissions wide, I opened them narrowly.
{
"permissions": {
"allow": [
"Bash(git status)",
"Bash(git diff:*)",
"Read(./content/**)"
],
"deny": [
"Read(./.env)",
"Read(./**/*.pem)"
]
}
}Read-only commands are allowed by name, and files holding secrets are closed off. Keep in mind, though, that each deny rule can pull compound commands that merely could touch its target into the approval queue as well (Issue #91650 again). A deny rule is not a free shield; it is paid for in daily keystrokes. For handling the prompts themselves inside headless runs, see Answering auto mode's confirmation prompts in headless runs.
One place to stop, and questions only during the hours someone is watching. Compressed to a sentence, that is the whole rule for me.
Move exactly one thing first
Look across the work you currently run interactively and find one job where you have never once pressed "yes" partway through. Move only that one into a scheduled slot, then leave it alone for a week. Moving the second one can wait until the first has seven days of logs behind it.
I may be missing something, but every time I redraw this boundary I notice how loose my previous version was. If you are stalled in the same place, I hope one piece of this is worth taking with you.