setup-channel-routine
Create or update the daily CLOUD routine (scheduled Claude Code agent) that runs a content-factory channel's growth agent on a cron schedule. Reusable across channels — LinkedIn, X, Instagram Reels, a
Files1
View on GitHubSKILL.md.claude/skills/setup-channel-routine/SKILL.md
GitHub/setup-channel-routine
Stands up (or updates) the daily cloud routine for a content-factory channel — a scheduled Claude Code agent that runs in Anthropic's cloud, clones the repo, runs the channel's growth-agent skill, commits, and opens a PR. One reusable recipe so every channel's routine matches the LinkedIn original.
Argument: the channel — linkedin | x | instagram (default: ask if missing).
Critical context: the fork the routine clones
Cloud routines do not read your local working copy. They clone altaf-shaikh-cs/content-factory — the FORK, which is the single integration point and the source of truth. A routine runs whatever is on the fork's main. The personal repo altafshaikh/content-factory (your local origin) is just a downstream mirror.
So before creating a routine, the channel's skill files MUST already be on the fork:
- Commit the channel skill + folder locally.
- Deploy to the fork:
bash scripts/deploy-code.sh.
If the channel skill isn't on the fork, the routine will fail or run stale. Always verify deployment (see Step 4).
Canonical routine config (identical for every channel)
| Field | Value |
|---|---|
environment_id |
env_01Vofgnx2PoR7qURGVMb4HcN (personal) |
| source repo (cloned) | https://github.com/altaf-shaikh-cs/content-factory (the FORK) |
allowed_tools |
["Bash","Read","Write","Edit","Glob","Grep","WebFetch","WebSearch"] |
mcp_connections |
none — clear them after create (the API auto-attaches all connected ones) |
| notifications | push on (the API default) |
enabled |
true |
| schedule | per-channel cron below, in UTC (user TZ is Asia/Calcutta = UTC+5:30) |
Channel registry (agent + default schedule)
Schedules are staggered so the daily runs don't collide.
| Channel | Growth-agent invocation | Default time (IST) | Cron (UTC) |
|---|---|---|---|
linkedin |
/linkedin-growth-agent with N=3 |
8:30 PM | 0 15 * * * |
instagram |
/instagram-reels-agent |
9:00 PM | 30 15 * * * |
x |
/x-growth-agent |
10:00 AM | 30 4 * * * |
If the user names a different time, convert IST → UTC (subtract 5:30) and confirm the conversion before creating.
Non-channel routine: Factory Health
Not a channel and it produces no content, but it uses the same config block, so it is set up here.
| Routine | Invocation | Default time (IST) | Cron (UTC) |
|---|---|---|---|
health |
/factory-health |
Mondays 9:00 AM | 30 3 * * 1 |
Its prompt is not the channel template above. Use this instead:
Run /factory-health.
You are the weekly health check for a multi-channel content factory. Follow .claude/skills/factory-health/SKILL.md exactly. You are READ-ONLY: do not generate content, do not write to raw-ideas/ or any channel folder, do not create a branch, do not open a PR, do not push anything.
Read the run logs in ./runs/, the open PR list on this fork (gh pr list --state open), and the LinkedIn unshipped register, then report which channels are dark, starved, or gated, which PRs are stuck in review, and the single highest-leverage next action. Your report is delivered as a push notification, so the first two lines have to carry the verdict on their own.
Because it is read-only and pushes nothing, this routine is safe to run as often as the user wants. Weekly is the default so it stays a signal rather than noise.
Prompt template (per run)
The cloud agent starts with zero context, so the prompt must be self-contained. The routine clones the fork (which is current — it's the source of truth), so no self-sync step is needed. The final block opens a within-fork PR for review, which works because the cloud's gh owns the fork.
Run <AGENT INVOCATION>.
<one-paragraph channel summary: what this channel does and where it writes — pull it from the channel's CLAUDE.md so the cloud agent has context>. Follow .claude/skills/<channel-skill>/SKILL.md and ./<channel-folder>/CLAUDE.md exactly. ./raw-ideas/ is immutable — never move, rename, or delete files there. Only write inside ./<channel-folder>/; cross-channel reads are read-only. Update the channel's TODO.md.
This repo IS the fork and the integration point. Before generating, run `gh pr list --state open --json headRefName` to see which ideas already have content in flight. Use the skill to select the single best next item to produce, and derive a STABLE kebab slug for it from its source raw-idea (NOT today's date), so the same idea always maps to the same branch: claude/<channel>-<slug>.
If that branch already has an open PR, or the skill determines there is nothing new worth producing, or a gate in the skill blocks the run, then STOP — do not create a branch or PR. Write the run-log row (see below) and report that there was nothing new. Otherwise:
1. git checkout -b claude/<channel>-<slug>
2. Stage and commit only the new/changed files with a clear message. Include the run-log row for this run (see below) in this same commit.
3. Push the branch to origin (the fork): git push -u origin claude/<channel>-<slug>
4. Open a pull request into the fork's own main for review:
gh pr create --base main --head claude/<channel>-<slug> \
--title "content: <channel> — <slug>" \
--body "Routine-generated <channel> content. Review and merge into the fork's main; the personal mirror syncs automatically."
This is a same-repo PR on the fork, which your gh account owns, so it succeeds. Do NOT touch the personal upstream.
HEARTBEAT — do this on EVERY run, including runs that produce nothing. It is the last thing you do. Append exactly one row to ./runs/<channel>.md following the contract in runs/README.md:
| <YYYY-MM-DD UTC> | <produced|skipped|blocked|error> | <one short line why> | <link to output or —> |
If you opened a PR, that row is already in the branch (step 2) and there is nothing more to do. If you did NOT open a PR, commit that single file on its own and push it straight to main:
git add runs/<channel>.md
git commit -m "runs: <channel> <YYYY-MM-DD> <outcome>"
git pull --rebase origin main && git push origin main
This single-line append is the ONLY thing you may ever push to main directly, and the commit must contain no other file. If that push fails, say so and finish anyway.
A quiet exit and a routine that never fired look identical from outside; two channels went dark for seven weeks under exactly that cover. A `skipped` row is a healthy result. A missing row is not.
Sync model (fork main = single source of truth)
The fork is the integration point; the personal repo is a downstream mirror. Three parts:
- Content (routine → fork PR): the routine picks the next item, derives a stable idea slug, and — only if no open PR already covers it and there's something new worth producing — pushes
claude/<channel>-<slug>to the fork and opens a within-fork PR into the fork's ownmain. Idempotent across days: an in-flight idea won't be duplicated. The cloud'sghowns the fork, so this same-repo PR succeeds (a cross-repo PR to the personal upstream would NOT — the work account isn't a collaborator there). - Code (local → fork):
bash scripts/deploy-code.shpushes committed local changes to the fork'smain. Nevergit push origin main(diverges the mirror). - Mirror (fork → personal):
bash scripts/mirror-personal.shfast-forwards personalmainfrom the fork. Runs daily at 11:00 AM via launchd; safe to run by hand.
Because personal main is only ever a fast-forward of the fork, the mirror never conflicts. The routine always clones current code because the fork is the source of truth.
Procedure
- Resolve the channel from the argument. If absent, ask which channel. Look it up in the registry for the agent invocation, default cron, and folder.
- Load the API tool:
ToolSearchwithselect:RemoteTrigger. (Auth is in-process — never use curl.) - Check for an existing routine:
RemoteTrigger {action:"list"}. If one already exists for this channel (match by name, e.g. "Daily X Post Creator"), ask whether to update it (action:"update", partial body) instead of creating a duplicate. - Verify the channel skill is deployed to the fork (so the routine won't run stale):
- Confirm the channel's
SKILL.md+ folder are committed locally. gh api repos/altaf-shaikh-cs/content-factory/contents --jq '.[].name'and check the channel folder is present on the fork. If not, runbash scripts/deploy-code.shfirst.
- Confirm the channel's
- Build the create body (generate a fresh lowercase v4 UUID for
events[].data.uuid):{ "name": "Daily <Channel> <Post|Reel> Creator", "cron_expression": "<cron from registry or user>", "enabled": true, "job_config": { "ccr": { "environment_id": "env_01Vofgnx2PoR7qURGVMb4HcN", "session_context": { "sources": [{"git_repository": {"url": "https://github.com/altaf-shaikh-cs/content-factory"}}], "allowed_tools": ["Bash","Read","Write","Edit","Glob","Grep","WebFetch","WebSearch"] }, "events": [{"data": { "uuid": "<fresh uuid>", "session_id": "", "type": "user", "parent_tool_use_id": null, "message": {"role": "user", "content": "<filled prompt template>"} }}] }} } - Create:
RemoteTrigger {action:"create", body:<above>}. - Strip auto-attached connectors: the create response usually lists every connected MCP connector. Immediately
RemoteTrigger {action:"update", trigger_id:"<id>", body:{"clear_mcp_connections": true}}so the routine matches the others (no connectors). - Report: print the routine id, the human-readable schedule (IST + UTC),
next_run_at, and the management URLhttps://claude.ai/code/routines/<id>. - Offer a test run: ask whether to
RemoteTrigger {action:"run", trigger_id:"<id>"}now to validate end-to-end before the first scheduled fire.
Notes
- Routines can be listed/updated/run but not deleted here — to delete, direct the user to https://claude.ai/code/routines.
- Min cron interval is 1 hour.
- Keep schedules staggered; check the existing routines' times before picking a new one.
- This skill only configures the cloud routine. The channel's generation logic lives in its own growth-agent skill — this skill just schedules it.
- After ANY repo change that the routine depends on, re-deploy: commit locally, then
bash scripts/deploy-code.sh(pushes to the fork). The personal mirror follows viascripts/mirror-personal.sh.