
A Claude Code routine publishes this blog end to end — hands-free, even with the PC off
A Claude Code routine translates, checks, and merges a drafted article on this blog end to end, with no machine needing to stay on.
On this page
Introduction
A Claude Code cloud routine has no AskUserQuestion tool. It cannot stop mid-run to ask a clarifying question. The previous round of shakedown tests confirmed that the hard way: anything handed to one has to arrive as a complete spec, with nothing left to ask about. The natural next step was to go further: take a drafted article all the way from a local file to a live, merged post, with nothing done by hand once it started and no machine needing to stay on for any of it. That now works — /blog cloud-publish hands a draft to a routine that translates it, verifies it, opens a pull request, and merges it once CI is green, entirely in the cloud. Building it surfaced three real problems first. A draft turned out to already be sitting on the default branch. An old article’s voice no longer matched this blog’s own current rules. A routine had more write access than the job needed. This article walks through each of those, the first real run that worked end to end, and the decision to remove the last manual step once its tradeoff was spelled out plainly.
The pipeline, start to finish
/blog cloud-publish <slug> pushes a draft to its own branch and opens a GitHub issue with the cloud-publish label already attached. That label fires a routine configured once, ahead of time, at claude.ai/code/routines. From there, the routine checks out that branch, translates every locale, and runs the same review gate the local pipeline runs. It opens a pull request, then merges it once CI passes. The routine’s own instructions stay generic: read the issue that triggered the run and do exactly what it says. The actual task, including “do not invoke /release, do the git and gh mechanics directly,” lives in the issue body, not in the routine’s configuration. That means the same routine serves any future publish request without being edited again.
The issue body itself is the actual task list the routine reads and executes, abridged here to the load-bearing steps:
## Task
1. Checkout branch `draft/<slug>`, not the default branch.2. Run `/blog publish <slug>` for every locale: translate, run the Section B review gate, flip `status` to `published`, verify with `pnpm check` and `pnpm build`.// … commit and push to the same branch5. Open a PR against `main` from `draft/<slug>` with `gh pr create` directly. Do **not** invoke the `/release` skill; it requires interactive confirmation this session has no way to give.// … wait for CI, then run a code review against the PR8. If CI is green **and** the review reported no `CONFIRMED` correctness finding, merge immediately: `gh pr merge --squash --delete-branch`.9. If the review reported a `CONFIRMED` correctness finding, fix it yourself and keep going. Two repair rounds is the whole budget for this PR.10. If a `CONFIRMED` finding still stands after the repair budget is spent, and CI is green, merge anyway, then open a follow-up issue naming what still stands.// … close the tracking issue once merged12. If CI is red at any point, repair it, and never merge it red. This is the one gate that actually stops the run.A draft that had already reached main
The first real test used an old, unfinished draft: python-yield-async-generator-agent-streaming-input, still marked status: 'draft'. It turned out to already be committed to main, left there by an earlier, unrelated release that had touched a mixed set of files. That is a genuinely safe state here (status: 'draft' keeps an article out of production builds regardless of which branch it sits on), but the first version of cloud-publish’s branching step did not account for it. It checked for uncommitted local changes and, finding none, would have reported nothing to hand off. That was wrong: there was a whole article ready to ship, just not sitting where the script expected to look.
The fix split the step into two cases. Uncommitted changes get staged, committed, and pushed to a new branch, the ordinary path fresh off drafting. Nothing uncommitted, but the article present, gets a branch cut directly from main with nothing new to commit: the same destination, reached without a commit that would not have described any real change.
An old draft that failed today’s own rules
Clearing that hurdle did not make the draft publishable. Running it through the current review gate surfaced two real defects that had nothing to do with the cloud pipeline at all. The title ran 105 characters against a 104-character hard ceiling. And the Introduction opened with almost exactly the pattern this blog’s own style guide names as banned: a second-person hypothetical standing in for a real incident. The line itself was “Building an AI agent, do you run into a question like this?” The rule calls for either a real first-person incident or a general statement with no pronoun at all.
Neither could be fixed by guessing. There was no session behind this old draft to read back into. The article turned out to be general knowledge rather than a build story anyway, confirmed rather than assumed, not guessed. So the fix followed the rule’s own stated alternative: rewrite the opening as a general statement with no pronoun, not invent a first-person incident that never happened. The same “you can…” framing recurred through several body sections and got the same treatment throughout. That was for consistency, not just to fix the one sentence a mechanical check happened to flag.
A connector with more access than the job needed

Setting up the actual routine surfaced a defect of a different kind. New routines include every currently connected connector by default. This one had picked up Gmail, Google Calendar, Google Drive, and a diagramming connector, and a routine that reads a GitHub issue and runs shell commands has no legitimate reason to touch any of them. The routine’s own edit screen states plainly what that means: Claude can use every tool from an included connector, writes included, with no approval prompt during a run.
Nothing about this job needed any of the four. They were removed before the routine was saved, on the same logic as scoping a permission grant to only the commands a skill actually issues: an unattended process should reach exactly what its task requires and nothing else. That matters most for a process whose only trigger is a label on a GitHub issue.
The first run that worked end to end

With both of those cleared, the actual run worked cleanly. The routine checked out the draft’s branch, translated it into Japanese and Traditional Chinese, ran the review gate against all three locales, and opened a pull request whose own summary reads like a competent handoff note: what it published, that it wrote the zh-TW translation from scratch, that it re-translated the Japanese to match the revised source rather than leaving it stale, and the specific pass conditions the source gate checked before any of it shipped.


Neither notification needed a machine on to arrive, or anyone watching to be useful: the first said the translation work was done, the second said it was safe to merge. This run predated automatic merge, so it stopped there as designed. The article was merged by hand afterward. It is live now, in the sitemap, in all three languages, published by a routine that never touched a local machine to get there.
Removing the last manual step
A pull request opened is not the same as an article live. The one manual step still standing was the merge itself. Removing it meant weighing a real tradeoff first. Nothing in this pipeline can mechanically verify that a translation is correct, only that it is structurally sound; pnpm check and pnpm build catch shape, not meaning. /release, this blog’s own general-purpose release flow, keeps its own merge step interactive for exactly that reason. That is on purpose: the one point in the whole flow it never removed.
The call to automate it anyway was made with that laid out plainly first, not defaulted into. I decided the convenience was worth it and said so directly. The reasoning behind removing the gate is recorded in the routine’s own instructions rather than left implicit, so a later reader would not mistake automatic merge for an oversight.
Summary
Handing a drafted article to a routine now reaches a merged, live post without a machine staying on for any of it. A real run confirmed it: translated, reviewed, opened as a pull request, and, after this session, set to merge on its own once CI passes. Getting there took three real fixes unrelated to the automation itself. A draft already on main needed its own branching case. An old article needed its voice actually rewritten rather than patched around. A routine’s default connector scope needed narrowing before it ever ran for real. What has not yet been proven end to end is the automatic merge itself. The one real run that validated this pipeline predates that change. The next real publish is also the first real test of it.

