CRAFT for Claude Cowork · Automation, With Receipts
How We Let AI Schedule Our Posts
Five automation tasks are built into this project’s marketing operation. Here is exactly what they do, how they were built, and how you could build the same on your own CRAFT for Claude Cowork.
An AI project that posts to social media on a schedule. If that sentence makes you suspicious, good — it should. The internet does not need more unattended robots talking into the void, and “the AI handles it” is exactly the kind of claim that deserves a hard look at what, precisely, is being handled.
So this post is the hard look, written by us about our own setup. It walks through the five tasks that run our posting, measurement, and backup operation — in enough detail that you could recreate each one yourself. Each section is written like one of CRAFT’s own recipes: what the task is, how it was built, how you can build it, why it exists, and the concrete specifics — file formats, credential field names, failure behavior — that separate “detailed enough to rebuild” from “trust us.”
How ours actually works
Every task in this suite follows the same five rules, and they are the actual content of this post — the code is just where they live:
- A human decides everything public. Nothing composes itself. The automation delivers text a person already wrote, reviewed, and approved — or it touches nothing public at all.
- Every run leaves a receipt. Logs, receipts, and archives — including for the runs where nothing happened, because a task that silently stops running is the failure you never hear about.
- Automated writers stay in their own lane. Any robot that writes to our version-controlled record does it on its own branch or by explicit, one-shot request — never as a second hand scribbling over the human’s work.
- Refuse rather than guess. When a check fails — an unapproved item, a diverged branch, a rule violation — the task stops and says so. No improvising.
- Someone checks up on the robots. A weekly review reconciles what was supposed to happen against what the receipts say happened, and reads the silences as findings.
The five tasks
- 1. The approved-queue social poster — publishes pre-approved posts to Bluesky, Mastodon, and X, and nothing else.
- 2. The daily metrics fetch — collects the numbers from six sources, read-only, into local snapshots.
- 3. The metrics-bot branch archiver — an automated writer that archives those numbers on its own branch, never the human’s.
- 4. The no-run heartbeat and weekly monitor — instrumentation for silence, and the review that reads it.
- 5. The session-close backup — an event-driven backup with a receipt the next work session must read before it trusts anything.
Why we’d bother
This is a small operation: one person’s limited weekly hours, several small platforms, and a framework — CRAFT for Claude Cowork — whose whole argument is that AI work should run with professional process discipline. The hours problem is real: pressing publish on already-approved text, pulling yesterday’s numbers, and checking that yesterday’s jobs actually ran are exactly the mechanical chores that eat a schedule without needing judgment. Those got automated. What to say, whether to say it, and what the numbers mean — the parts that need a person — stayed with a person.
There is also a second reason, and it is the more honest one: we build and publish process discipline for AI work, so our own automation has to be the demonstration. Our standing rule is that AI automation should always be trust-but-verify. That is why every task below leaves receipts, why one of the five tasks exists only to check the other four, and why this post tells you about the failure handling and not just the happy path.
What you’d need
Everything below runs on ordinary equipment: CRAFT for Claude Cowork (free during beta — the download is ungated), a computer that is on when your schedules fire, and the accounts you already have on whichever platforms you use. Not every task below is a Claude Cowork scheduled task in the literal sense — the poster runs as one, two ride the computer’s own scheduler, one half lives in GitHub, and the backup fires on a file event — but all five were built, wired, and are governed from CRAFT sessions, and each section says exactly which runner it uses. A few sources need a small credential — each section lists exactly which. None of it requires being a programmer: in every case, the working method was to describe the guardrails to Claude inside a CRAFT session and let it build the mechanism, then review what it built.
Task 1 of 5 · Distribution
The approved-queue social poster
A scheduled task that publishes only pre-approved, frozen posts to Bluesky, Mastodon, and X — and does nothing else.
What was created
A small, single-purpose poster (post_queue.py) plus the folder convention it reads. Approved posts live as one plain-text file each in an approved-queue/pending/ folder, with a short header (which platform, an optional “not before” time, and who approved it and when). The poster’s entire job is to look at that queue, deliver anything that is due, and leave a receipt. Two more folders — posted/ and rejected/ — hold what went out and what got refused, and a running POSTING-LOG.md records every delivery.
How the AI did it
Inside a CRAFT for Claude Cowork session, Claude wrote the poster in plain standard-library Python (no frameworks to install), wired it to the queue convention above, and added a set of “refuse rather than guess” checks. It reads its credentials only from a protected secrets folder, never from the conversation. Then it was registered as a Claude Cowork scheduled task so it runs on its own. The authoring and the approving stayed human; only the delivery was handed to the machine.
How you can do this
On your own CRAFT for Claude Cowork you can stand up the same thing without writing code yourself — describe the guardrails you want and let Claude build the poster and the folders, then approve posts in plain language. You want a folder of approved posts, a poster that only delivers approved-and-due items and logs every run, credentials kept out of chat, and a dry run before it goes live. The exact fields, formats, and setup steps are in the box below.
Why it was done
CRAFT’s stance is trust-but-verify, and the honest way to hold it is to automate the boring, mechanical part (pressing publish on already-approved text) while keeping the parts that need judgment — what to say and whether to say it — with a person. Making every run leave a receipt, including the runs where nothing was due, means a task that silently fails to fire shows up as a gap in the record instead of disappearing quietly.
How it fits the larger project goals
The marketing operation needs steady, staggered distribution across several small platforms without a human hand on every button. This task provides that, under CRAFT’s own process discipline, and produces the raw record that the monitoring task (Section 4) later reconciles. It is the outbound end of the same system whose inputs the metrics tasks (Sections 2–3) collect.
Replication detail (enough to rebuild it)
1. A queue item
One markdown file per post in pending/, with simple key: value front matter between two --- fences; the text below posts verbatim:
2. The gates, in order
- Skip if not approved (missing
approved_by/approved_on). - Skip if
not_beforeis still in the future. - Run content checks (a date-format guard, a banned-word guard, an approximate-counts guard — these are your own policy to define); a failure is moved to
rejected/with the reason written on it. - Enforce length limits — X 280 · Bluesky 300 · Mastodon 500 characters (platform defaults as of writing; Mastodon’s is instance-configurable).
- Never post two identical texts in the same run; post at most one item per platform per run.
3. Credentials (per platform)
Kept as .env files in a git-ignored .craft-secrets/ folder; the script reads the field names below and nothing else:
- Bluesky (
bluesky.env) —BSKY_HANDLE,BSKY_APP_PASSWORD(an app password from your Bluesky settings; free). - Mastodon (
mastodon.env) —MASTODON_INSTANCE,MASTODON_ACCESS_TOKEN(an access token from your instance’s developer settings; free). - X (
x.env) —X_API_KEY,X_API_SECRET,X_ACCESS_TOKEN,X_ACCESS_TOKEN_SECRET(OAuth 1.0a). Note: unlike the other two, posting through X’s API is not free — it bills per request from prepaid usage credits (a small per-post charge, more for posts that carry a link; check X’s current developer pricing — it changes).
4. Receipts and retries
On success the poster appends a row to POSTING-LOG.md and moves the file to posted/; on failure the item stays in pending/ and is retried on the next run (there is no automatic give-up — a persistently failing item keeps retrying until it posts or you remove it, which is where you’d add a cap if you wanted one). Every run — even one with nothing due — writes a heartbeat line, so a missing day is a visible gap:
5. Schedule it
Register it as a Claude Cowork scheduled task that runs python3 automation/post_queue.py, and choose a cadence that fits your posting volume — for a low-volume queue, once or a few times a day is plenty. The one-post-per-platform-per-run throttle plus each item’s not_before stamp produce the stagger, so you don’t need a busy schedule. Run python3 automation/post_queue.py --dry-run first — it prints exactly what would post and why the rest would not, touching nothing. Registration itself is conversational — ask Claude in your session to set up the scheduled task with the command and cadence you chose, and it confirms the schedule back to you. (We keep our own exact posting times private; pick yours to fit your queue.)
FAQ
Can the automation write its own posts?
No. It composes nothing. It can only deliver a file a human already approved; an item not marked approved is skipped.
Does it read or reply to anyone?
No. It is outbound only, with no permission to read replies or respond — anything conversational is handled by a person.
What happens if a post breaks a rule?
The content checks refuse it: the file is moved to rejected/ with the reason, and nothing goes out. The checks are a backstop; the real gate is the human approval before the item ever enters the queue.
Does posting to X really cost money?
Yes — X’s API bills posting per request from a prepaid credit balance, and posts that include a link cost more than plain ones. Bluesky and Mastodon posting are free. You can run the poster with only the platforms you have set up.
How would I know if it silently stopped running?
Every run writes a heartbeat line, including runs where nothing was due, so a missing day is a visible gap — which the weekly monitoring task (Section 4) is built to catch.
Do I need to be a programmer?
No. You describe the guardrails you want and Claude builds the poster and folders; you approve posts in plain language.
Task 2 of 5 · Measurement
The daily metrics fetch (read-only)
A scheduled task that collects yesterday’s numbers from six sources into local snapshot files — and has no ability to post, publish, or change anything public.
What was created
Six small fetchers — one per source — and an orchestrator that runs them all. Each fetcher pulls one source’s numbers (site analytics, search impressions, repository traffic, article stats, follower counts) and writes two things: a dated JSON snapshot with the full detail, and a single summary row in that source’s rollup file, one row per day. The orchestrator runs the six in sequence and appends a run record — which fetchers ran, which succeeded, which failed and why — to a pipeline log. Everything lands as plain files in a project/metrics/ folder; nothing leaves the machine.
How the AI did it
Inside a CRAFT for Claude Cowork session, Claude wrote each fetcher as a separate small Python script — standard library only where the platform allows it (four of the six), with Google’s client library for the two Google sources. The design decision that matters is fault isolation: the orchestrator runs each fetcher as its own process with a five-minute timeout, so one source being down, misconfigured, or missing its key never blocks the other five. A partial run still counts — the task only reports failure if every single source failed. The second decision is the one you should copy even if you copy nothing else: the whole pipeline is read-only by construction. The task that measures has no code paths that publish.
How you can do this
Tell Claude which platforms you use and ask for the same shape: one fetcher per source, each writing a dated snapshot plus a one-row-per-day rollup, an orchestrator that isolates failures, and a run record that says what happened. Ask for read-only explicitly — it is a property you want to be able to state, not an accident. The formats, credentials, and scheduling specifics are in the box below.
Why it was done
Marketing decisions want numbers, and numbers want collecting before you need them — several of these sources only expose recent history, so the day you finally want a trend line is too late to start. A daily snapshot turns every API’s short memory into a durable local record. Just as important is what this task cannot do: keeping measurement and publishing in separate tasks with separate permissions means the thing that reads the internet all day has no ability to write to it.
How it fits the larger project goals
This is the input side of the operation. The weekly working session reads these rollups to decide what is working; the archiver (Section 3) preserves them in version control; the monitor (Section 4) uses the same read lanes to spot-check that published posts are actually live. One collection task, several consumers, no duplication.
Replication detail (enough to rebuild it)
1. The file layout
One folder per source; each daily run writes a dated snapshot and replaces that day’s rollup row (re-running a day is harmless — the row is replaced, not duplicated):
2. Credentials, per source
- Bluesky — none. The public API (
public.api.bsky.app) serves profile and feed stats without auth; the config file just holds your handle. - Mastodon — none. Public
/api/v1endpoints on your instance; the config holds the instance domain and account name. - GitHub — a fine-grained personal access token, read-only, in a one-line token file (
github.token); the config lists the owner and repositories to read. - dev.to — a single API key in
devto.env(sent as anapi-keyheader; the fetcher also accepts aDEVTO_API_KEYenvironment variable). - GA4 — a Google service-account JSON file, plus a config listing your property id(s) and a lookback window. Needs Google’s
google-analytics-dataPython package. - Search Console — reuses the same service account (grant it access in Search Console); the config lists your site URL(s). Fails closed — reports itself blocked, harmlessly — until the grant exists.
All keys live in a git-ignored .craft-secrets/ folder as small files — never in the conversation, never in the repository.
3. Failure behavior
Each fetcher runs as its own process with a 300-second timeout; per-fetcher status lands in the run record as OK, FAIL(code), TIMEOUT, or MISSING. One failure never blocks the rest, a failed source simply has no snapshot that day, and the orchestrator itself exits as failed only when every source failed — so downstream steps can still use a partial day.
4. Schedule it
Ours runs as a launchd agent — macOS’s built-in scheduler — because the snapshots should land on the machine that owns the record. The one-time registration: save a small .plist into ~/Library/LaunchAgents/ and load it once with launchctl. The essential keys:
Pick any early-morning hour that suits you — the value above is an example shape, and daily is plenty. launchd coalesces missed runs: if the machine was asleep, the run fires once at the next wake instead of piling up. If your setup lives entirely in Claude Cowork, the same fetchers can run as a Claude Cowork scheduled task instead; we chose the native scheduler so the record-keeping stays on the record-keeping machine.
FAQ
Why insist on read-only?
Separation of powers. The task with credentials to read six services should have no ability to write to any of them — then a bug, a bad config, or a compromised key in this task cannot publish anything.
Do the social fetchers need my passwords?
No. Bluesky and Mastodon follower and post counts come from public, unauthenticated endpoints — those two fetchers hold no secrets at all.
What happens when one source is down?
Its fetcher fails alone, the run record says so, and the other five still write their snapshots. The task only counts as failed when everything failed.
Why both a snapshot and a rollup?
The dated JSON keeps full detail for the day you need to dig; the rollup gives one glanceable row per day for trend-reading. Two consumers, two shapes, same fetch.
What if the computer is asleep at fetch time?
The run fires at the next wake. If it never fires at all, that day is a visible gap in the run record — which the weekly monitor (Section 4) treats as a finding, not noise.
Do I need to be a programmer?
No. Describe your sources and the two-file output shape to Claude in a CRAFT session and review what it builds; the registration step is copying one small file and running one command.
Task 3 of 5 · Record-keeping
The metrics-bot branch archiver
An automated writer that saves the daily numbers into version control — on its own branch, in its own working copy, under its own name, and never on the human’s branch.
What was created
First, the rule this task exists to obey, because the rule came before the code: an unattended automated writer gets its own branch — it is never the writer-of-record for the human’s branch. Our version-controlled project record lives on main, and exactly one writer — the human’s machine — writes it. So the archiver got its own lane: a metrics-bot branch, checked out in a separate git worktree, where a robot may commit all day without ever touching the human’s history.
The task has two halves. The local half runs on the Mac each morning after the fetchers: it commits the day’s snapshots and rollups to metrics-bot and pushes. The cloud half is a small scheduled GitHub Action that snapshots repository traffic — stars, forks, clones, views, referrers — into the same branch, because GitHub’s traffic API only keeps about fourteen days of history and a daily archive is the only way to own more than two weeks of it.
How the AI did it
Claude wrote the local runner as a ladder of refusals, inside a CRAFT for Claude Cowork session. In order: confirm the worktree is actually on the bot’s branch (wrong answer stops the run); pull fast-forward-only (a diverged branch stops the run — reconciling history is a human job); run the fetchers (if every source failed, commit nothing); stage only the metrics folder (so nothing else can ever ride along); commit under the bot’s own name; push; and then — the step most setups skip — ask the remote what its branch head is now, and treat “the push claimed success but the remote did not advance” as a failure. Stray git lock files get moved aside, never deleted. The push token is handed to git through GIT_ASKPASS so it cannot appear in a command line, a URL, or a log.
How you can do this
Ask Claude for an own-branch archiver: a dedicated branch, a dedicated worktree, and a runner that refuses in every ambiguous situation. If you keep anything machine-generated in a repository a human also works in — metrics, logs, snapshots — this is the pattern that keeps the robot and the human from writing over each other. The concrete steps are in the box below.
Why it was done
Because two writers on one branch is how a project record gets corrupted politely: no crash, no error, just a morning where the human’s history has a robot’s commits braided through it and every session starts with archaeology. One writer per branch makes the question “who wrote this?” structurally unaskable. The traffic snapshots have a second, plainer reason: data you do not archive within fourteen days is data you no longer have.
How it fits the larger project goals
Section 2 collects the numbers; this task makes them durable and inspectable — every day’s numbers are a commit, so trends survive machine failures and the record itself has a history. And by absorbing all robot writes onto its own branch, it is also what keeps main clean for the one automation allowed to touch it: the human-requested session-close backup (Section 5).
Replication detail (enough to rebuild it)
1. The branch and the worktree (one-time)
No Mac? The local half is scheduler-agnostic — cron or Windows Task Scheduler can drive the same runner on the same worktree.
If your secrets folder is git-ignored (it should be), the fresh worktree will not have it — symlink it in from the main checkout, and keep the metrics staging scoped so the link can never be committed.
2. The runner’s refusal ladder (the local half)
- Assert the current branch is
metrics-bot; anything else is a fatal stop. git pull --ff-only— a diverged branch stops the run for a human.- Run the fetchers; if every source failed, stop — commit nothing.
git addthe metrics folder only — never-A.- Commit as the bot’s own identity (its own name and email), push the branch.
- Verify with
git ls-remotethat the remote branch head now equals the local head — a push that did not advance the remote is a failure, whatever its exit code said. - Move stray
index.lock/HEAD.lockfiles aside to a temp folder; never delete them.
3. Credentials
The local half pushes with a personal access token kept in a one-line git-ignored file and supplied via GIT_ASKPASS (a two-line helper script that prints the token as the password). The Action half needs a fine-grained token with read-only access to the repositories whose traffic you snapshot, saved as a repository Actions secret (ours is named TRAFFIC_PAT) — the workflow’s own permission to push comes from the standard Actions token, scoped contents: write.
4. The Action (the cloud half)
One quirk worth knowing: GitHub reads the workflow file from your default branch, but the job checks out and commits to metrics-bot — so keep the collector script present on the bot branch too.
5. Failure behavior
Every refusal above exits with a logged reason rather than improvising; “nothing changed today” is a clean exit, not an error. A missed local run coalesces to the next wake; a missed Action run simply leaves that day’s snapshot absent — and a gap in dated files is exactly the kind of silence the weekly monitor (Section 4) reads. One hazard from our own experience: if you ever stop tracking the metrics folder on the human’s branch, keep that ignore rule off the bot’s branch — the bot must keep tracking what it archives, and recovery of metrics data means pulling the bot’s branch, not a copy of main.
FAQ
Why not just have the bot commit to main?
Because then main has two writers, and every human session begins by asking what the robot did overnight. One writer per branch removes the question entirely.
Why a separate worktree instead of switching branches?
Switching branches in the human’s checkout would change files out from under whoever is working. A worktree gives the bot its own directory, permanently on its own branch, in the same repository.
Why verify the push with ls-remote?
Because exit codes flatter. We treat “the remote branch head now equals mine” as the only proof a push landed — the same never-trust-a-claimed-success habit the session backup (Section 5) uses.
What happens if the branch diverges?
The fast-forward-only pull fails, the run stops, and the log says so. Reconciling divergent history is a decision, and decisions are not delegated to a cron job.
Do I need the GitHub Action half?
Only if you want repository traffic history — that API forgets after about fourteen days, and only a scheduled archiver defeats that. The local half stands alone otherwise.
Do I need to be a programmer?
No. The concepts above — own branch, worktree, refusal ladder, verify-the-push — are the specification. Hand them to Claude in a CRAFT session and review the script it writes against that list.
Task 4 of 5 · Oversight
The no-run heartbeat and the weekly monitor
Instrumentation for silence: a log line for every run — including the runs where nothing happened — and a weekly review that turns missing lines into findings.
What was created
Three small artifacts that only make sense together. First, a heartbeat: the social poster (Section 1) appends one line to a log on every live run — including runs where nothing was due to post. Second, a written expectation: when a batch of posts is approved and queued, a plain-text file records which runs are supposed to happen. Third, a weekly monitor: a CRAFT recipe that reconciles the two — expectation against heartbeat, plus the rest of the receipts — and produces a short reliability report for the human.
How the AI did it
The design came out of one observation Claude and the operator arrived at while planning the suite: the worst automation failure leaves no trace. A task that runs and fails writes an error somewhere. A task that never fires — machine asleep, app closed, schedule silently dead — writes nothing, and reconciling receipts cannot catch a receipt that does not exist. The fix is to instrument for silence: make every run write a line even when it did nothing, so that absence becomes a visible date gap. The second design rule followed immediately: a reconciliation needs both sides on disk. “What was supposed to happen” must be a file, written when the plans are made — never reconstructed from memory at review time. Within a CRAFT for Claude Cowork setup this became a few lines added to the poster, a file convention, and a recipe the AI executes each week.
How you can do this
Ask Claude to add a heartbeat line to every scheduled task you run, to write an expectations file whenever future runs are planned, and to build you a weekly review recipe that reconciles the two and walks the rest of your receipts. The formats and the checklist are in the box below — they are deliberately boring, which is the point.
Why it was done
Our standing rule is that AI automation should always be trust-but-verify, and a rule you do not enforce on yourself is a slogan. Sections 1–3 of this post describe tasks that run unattended; this task is what makes “unattended” honest — a person, once a week, reading a report built from receipts the tasks cannot help leaving. When something fails, the report says so plainly, and the fix feeds back into the setup.
How it fits the larger project goals
This is the oversight layer over everything else in this post. It consumes the poster’s logs (Section 1), reuses the metrics pipeline’s read lanes for spot-checks (Section 2), and its findings decide what gets repaired or rebuilt in a given week. It is also the task that makes the others improvable: you cannot fix failure modes you never see.
Replication detail (enough to rebuild it)
1. The heartbeat line
One line, appended by every live run of the monitored task, whatever happened:
2. The written expectation
When posts are approved and queued, append one line per expected posting slot to SCHEDULE-EXPECTED.txt — day, platform, queue id. That file is the review’s source of truth for “what was supposed to happen.” If it is missing or stale for the review window, that is itself the first finding of the week — fall back to the queue items’ own not_before stamps and say so in the report.
3. The six weekly checks
- Did every scheduled run fire? Compare the heartbeat’s dates against the expectation file. Distinguish LATE (the machine woke and the run fired later the same day) from ABSENT (no line at all) — they are different failure classes with different fixes.
- Per-post outcomes. Reconcile the posted and rejected folders against the posting log. A rejection at the automation’s lint layer means the human-review gauntlet upstream missed something — record it as a process finding, not just a statistic.
- Platform spot-verify. Re-fetch each posted URL through the read-only lanes and confirm the post is actually live — the deleted-or-silently-failed class that logs alone cannot see.
- Aging. List queue items still pending well past their scheduled time (ours flags at about two days) — stuck approvals and schedule drift. List, do not fix.
- Spend. One platform’s API costs real money (Section 1’s note about X). Estimate month-to-date posting spend against a small prepaid-credit cap — ours is a few dollars, with automatic top-up off — and flag at half the cap.
- The human lane. Our blog posts are published by hand, outside these five tasks, on purpose; the review just confirms the expected posts exist at their addresses. Note only — no automation reaches into that lane.
4. The report
Half a page, internal: the week in one paragraph (scheduled / fired / posted / failed / fixed) · a per-platform table · failures in full — what happened, why if known, what changes · the gap analysis · the spend line · stuck items. The failures section is the one that earns its keep: written plainly, it becomes the to-do list that improves the suite.
5. Schedule it
The heartbeat needs no scheduling — it rides the tasks it instruments. The weekly review can be a recurring Claude Cowork scheduled task or simply the first recipe of a weekly working session; ours is the latter. Weekly is the right grain for a small operation — tight enough that a dead schedule cannot rot for a month, loose enough that the review stays a review and not a chore.
FAQ
What is the worst failure this catches?
The silent no-run — a task that simply stopped firing. Error handling cannot catch it because nothing errored; only an expected-versus-actual reconciliation sees it.
Why log runs where nothing happened?
Because “no line today” must be allowed to mean only one thing: the task did not run. If quiet days wrote nothing, a dead task and a quiet day would look identical.
Why keep the expectation in a file instead of just remembering the schedule?
A reconciliation needs both sides on disk. Memory drifts, plans change mid-week, and a review that checks receipts against a recollection is checking nothing.
Is the review itself automated?
The checks are mechanical and the AI runs them; the reading is not. A person reads the report weekly — that is the entire point of it existing.
What do you do with the findings?
Fix what is fixable, record what changed, and let the failure list steer that week’s maintenance. The receipts make the automation improvable, not just observable.
Do I need to be a programmer?
No. The heartbeat is a one-line addition Claude can make to any task it built for you, and the review is a checklist — the box above is the specification.
Task 5 of 5 · Continuity
The session-close backup (and its receipt)
An event-driven backup that commits a work session’s files when — and only when — it is asked to, then writes a receipt the next session must read before it trusts anything.
What was created
A handshake between two machines that trust each other exactly as far as a receipt. Our CRAFT for Claude Cowork sessions run in the cloud, but the project’s version-controlled record lives on the local machine — and the AI does not run git against that record, as a standing rule. So closing a session works like this: the AI writes its session files through the file bridge (the Claude desktop app’s connected local folder), then drops one small JSON file — the sentinel — containing the commit message. On the local machine, a watcher notices the sentinel appear and fires a script exactly once. The script commits and pushes, writes a receipt saying exactly what happened, and deletes the sentinel. The next session’s first act, before writing anything, is to read that receipt.
How the AI did it
Claude wrote the script and the watcher registration inside a CRAFT session, around one non-negotiable: this automation is the human’s own close commit, mechanized — not a second writer with opinions. That shaped everything. It is event-driven, never scheduled — a timer would be a robot deciding when to write. It is one-shot — the sentinel is consumed on firing. It commits what the work session left changed — it composes nothing of its own. And it refuses in every ambiguous case: it will not run off the main branch, it moves stray git locks aside rather than deleting them, and before committing it fetches and checks ancestry — if the remote has commits the local copy lacks, it stops and writes a failure receipt rather than force anything. Even success is verified rather than assumed: after pushing, it asks the remote for its branch head and only writes ok:true if the remote actually advanced.
How you can do this
Ask Claude for a sentinel-triggered close backup with a receipt: a watcher on a folder, a script with the refusal ladder above, and a boot rule in your project instructions that reads the receipt before any new work. The formats and the registration step are in the box below.
Why it was done
Because the failure that costs the most is the one you build on. A backup that fails at 11 p.m. is an inconvenience; a next morning’s session that assumes the backup landed and writes on top of the assumption is how a project record forks quietly. The receipt closes that loop: ok:true with a new commit hash means the close landed; anything else — a failure, a missing receipt, a stale hash — means reconcile first, write second. Silence is not an acceptable outcome for a backup, so the script writes a receipt on every path, including the failures.
How it fits the larger project goals
This is the continuity spine of the whole operation. Every artifact the other four tasks produce — queue receipts, logs, snapshots, reports — matters only if the project record itself survives sessions, machines, and mistakes. It is also the pattern in miniature: the human’s branch keeps exactly one writer, automation acts only on explicit request, and every action leaves evidence the next actor is required to check.
Replication detail (enough to rebuild it)
1. The sentinel
The AI (or you) drops one small file to request a backup; the message becomes the commit message:
2. The receipt (written on every outcome)
The companion rule lives in your project instructions: the next session reads this file before its first write. A receipt nobody reads is decoration.
3. The watcher (one-time registration)
A launchd agent with WatchPaths pointed at the folder the sentinel lands in — event-driven, so there is no polling and no timer:
The pattern ports: on other systems, any file-watcher that can run a script — even a scheduled job that polls for the sentinel — replaces WatchPaths.
4. The script’s refusal ladder
- No sentinel? Exit silently — the watcher fires on any change to the folder.
- Not on the expected branch? Stop; write a failure receipt.
- Stray
index.lock/HEAD.lock? Move aside to a temp folder; never delete. - Fetch, then ancestry check: if the remote is ahead of or diverged from local, stop and write a failure receipt — never force, never clobber. Reconciling is a human decision.
- Stage everything the session left changed — ours uses
git add -A, workable because this working copy only changes through session work and every robot writer lives on another branch (Section 3) or outside tracking; scope the staging to named folders if your repository is busier. If nothing changed, write anok:truenothing-to-commit receipt and finish. - Commit with signing disabled for this commit — a headless agent has no way to answer an interactive signing prompt, and a hung prompt is a silent failure.
- Push, then verify: local head must equal the remote head (
git ls-remote). Only then is the receiptok:truewith the hash. - Delete the sentinel — one request, one attempt, one receipt.
5. Credentials and failure behavior
The push token lives in a one-line git-ignored file and reaches git only through a GIT_ASKPASS helper — never in a remote URL, never in a log. Every failure path writes an ok:false receipt with the reason; the sentinel is consumed either way, so a failed close never fires again on its own — it surfaces at the next session’s boot check instead, where a human decides. There are no retries by design: retrying a refused push is how clobbering happens.
FAQ
Why not let the AI just run git itself?
Lane separation. The cloud session authors files; the local machine — the record’s one writer — commits them, on request, with the push token never leaving that machine.
Why event-driven instead of a nightly timer?
A timer commits whatever it finds, whenever it fires — that is a second writer with a schedule. Firing on an explicit request keeps every commit intentional and attributable to a session close.
Why would the remote ever be ahead of the local copy?
Rarely and legitimately — work landed from elsewhere, a manual fix pushed directly. Whatever the cause, it means histories need reconciling, which is precisely what an unattended script must not attempt.
What happens when the backup fails overnight?
The receipt says so, and the next session’s boot rule reads the receipt before writing anything — so the failure is the first item of business instead of a surprise three days later.
Is a receipt file really better than just checking git?
It is the contract, not a substitute for git — the next session can (and ours does) confirm the hash against the actual log. The receipt’s job is to make the outcome unambiguous and cheap to check from anywhere, including a cloud session with no direct git access.
Do I need to be a programmer?
No. The refusal ladder above is the specification — give it to Claude in a CRAFT session, review the script against the list, and test it once with a throwaway commit before trusting it.
Wrap-up · Automation, With Receipts
The sum of it
Five tasks, one shape: humans decide, automation delivers, everything leaves a receipt, and somebody reads the receipts.
Lay the five side by side and the pattern is hard to miss. The poster cannot compose; it can only deliver what a person approved. The metrics fetch can read six services and write to none. The archiver may write all day — on its own branch, under its own name, never on the human’s. The backup touches the human’s branch only when explicitly asked, once, and proves it. And the monitor exists to catch the others failing — including the failure mode that leaves no evidence, which is why the evidence is manufactured on purpose, every run, even the empty ones.
None of this is a claim that the automation will not break. It breaks. Machines sleep through schedules, APIs change, a push occasionally fails at 11 p.m. The claim is narrower and, we think, more useful: when it breaks, the receipts say so, quickly, and a person is looking. That is what trust-but-verify means once you take it seriously enough to write it into file formats.
What this says about the framework
Everything in this post was built and is governed the same way the rest of this project is run: inside CRAFT for Claude Cowork sessions, under its process discipline — the guardrails described to the AI first, the mechanism reviewed against them, the rules written down where the next session reads them. The scheduled tasks are not a separate cleverness bolted onto the framework; they are what the framework’s habits — single writers, receipts, refuse-rather-than-guess, verify your own work — look like when pointed at a small, real marketing operation. If the approach in these five sections appeals to you, that is the product, demonstrated on ourselves.
Try it on your own projects
CRAFT for Claude Cowork is free during beta, and the download is ungated — no account, no email, no checkout. Every section above was written to be reproducible on your own setup, and the framework you would use to do it is the same one that built ours.
Download CRAFT for Claude Cowork free →
