# Where should I host small automations that Claude wrote for me?

> How to choose a host for a small script Claude wrote: your own machine, GitHub Actions, Cloudflare Workers, a managed cron service, or a platform that runs Claude workflows.

- Published: 2026-10-26
- URL: https://teton.so/guides/where-to-host-small-automations-claude-wrote

For most small scheduled scripts Claude writes, such as a daily report, a sync or a Slack nudge, GitHub Actions on a schedule is the easiest free place to start. If the script is JavaScript or TypeScript and only calls APIs, Cloudflare Workers Cron Triggers are faster and more punctual. Use a managed cron service like Render when the job needs a real server environment, and keep it on your own machine only while you are still testing. If you built the job by talking to Claude and want it to keep running without owning a repo or server, a platform that runs Claude workflows in the cloud is the other option.

Whichever host you pick, three things matter more than the host: secrets kept out of the code, some way to notice a run that never happened, and somewhere to store state between runs.

## Questions to answer before you pick a host

- **Language.** Python with `pip` dependencies fits GitHub Actions, a managed cron service or your own machine. Plain JS or TS that calls APIs fits Cloudflare Workers.
- **Trigger.** A schedule works everywhere. If the job must react to an event, such as a form submission or a webhook, you need a host that can receive HTTP requests.
- **Runtime.** A job that runs for seconds fits anywhere. Long scraping or large exports need a host with generous time limits.
- **State.** "Compare with yesterday" or "don't send the same alert twice" needs storage that outlives the run.
- **Who maintains it.** Someone has to rotate tokens, fix failures and notice when the job goes quiet.

## 1. Your own machine (for testing)

Use `crontab -e` on Linux or macOS, a `launchd` job on macOS, or Task Scheduler on Windows. It is free and you can see everything. It only runs while the machine is on and awake, which makes it a poor home for anything a team depends on. Use it to confirm the script works on a schedule, then move it.

## 2. GitHub Actions on a schedule

This is the most common recommendation for good reason. The workflow lives next to the code, secrets go in repository settings, and [public repositories run free on standard runners](https://www.cicdpipelinecost.com/github-actions-pricing). Private repositories get a monthly allowance of included minutes, [2,000 on Free and 3,000 on Pro and Team](https://www.warpbuild.com/answers/are-github-actions-free-for-private-repositories) at the time of writing. Some older posts quote different figures, so check GitHub's billing docs for your plan.

Save this as `.github/workflows/daily-report.yml`:

```yaml
name: daily-report
on:
  schedule:
    - cron: "17 13 * * 1-5"   # 13:17 UTC, weekdays
  workflow_dispatch:          # adds a manual "Run workflow" button
jobs:
  run:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - run: python report.py
        env:
          HUBSPOT_TOKEN: ${{ secrets.HUBSPOT_TOKEN }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          HEARTBEAT_URL: ${{ secrets.HEARTBEAT_URL }}
```

Add the secrets under **Settings > Secrets and variables > Actions**.

Know the limits before you rely on it. GitHub's docs say [scheduled runs can be delayed under high load, especially at the start of every hour, and some queued jobs may be dropped](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows). That is why the example runs at minute 17 instead of minute 0. Schedules only run from the default branch, and [workflows in forks stay disabled until you enable them](https://devopsjournal.io/blog/2022/08/12/workflows-not-starting). In public repositories, scheduled workflows are turned off after 60 days with no repository activity, and a community answer notes that [this happens without an email or warning](https://github.com/orgs/community/discussions/202111).

**Good for:** Python scripts that run a few times a day where a delay of some minutes doesn't matter.

## 3. Cloudflare Workers Cron Triggers

If Claude wrote the job in JavaScript or TypeScript and it mostly calls APIs, a Worker with a cron trigger is lightweight and starts close to on time. One summary of the current limits puts it at [5 cron triggers per account on the Free plan and 250 on Paid, a 15-minute wall-clock limit per scheduled run, and 10 ms of CPU per run on Free](https://flaviocopes.com/cloudflare-cron-triggers/). Time spent waiting on `fetch()` doesn't count toward CPU. Older posts give a different per-Worker number, so confirm the limits on developers.cloudflare.com.

`wrangler.toml`:

```toml
name = "daily-report"
main = "src/index.js"
compatibility_date = "2024-09-01"

[triggers]
crons = ["17 13 * * *"]
```

`src/index.js`, written in the service-worker format, where secrets are available as globals:

```js
addEventListener("scheduled", (event) => {
  event.waitUntil(postToSlack());
});

async function postToSlack() {
  const res = await fetch(SLACK_WEBHOOK_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ text: "Good morning from the scheduled Worker" }),
  });
  if (!res.ok) throw new Error(`Slack returned ${res.status}`);
}
```

Cloudflare recommends the newer ES module format for new Workers, where the file default-exports an object with an `async scheduled(controller, env, ctx)` handler and reads the secret as `env.SLACK_WEBHOOK_URL`. Either format works with cron triggers; ask Claude for the module version if you plan to add KV or D1 bindings.

Store the webhook with `npx wrangler secret put SLACK_WEBHOOK_URL`, then `npx wrangler deploy`. Triggers run in UTC, and [only the most recent events are kept in the dashboard](https://crontap.com/blog/cloudflare-workers-cron-minute-limit). For state, Workers pair with KV or D1.

**Good for:** frequent, short API-to-API jobs in JS or TS. It is not a good fit for Python with heavy dependencies.

## 4. A managed cron service (Render and similar)

Platforms like Render can run [web apps, cron jobs, background workers and managed databases](https://www.python-engineer.com/posts/hosting-platforms-for-python/) in one place. A cron job there is a normal container with a schedule, so long runtimes and native dependencies are less of a problem. These are usually paid, so check current pricing. Free hosting lists go out of date quickly. The page above still recommends Deta, which has since shut down.

**Good for:** jobs that outgrow Actions or Workers, or teams that already host apps there.

## 5. Google Apps Script

If the automation lives entirely in Gmail, Sheets or Drive, Apps Script's time-driven triggers run it with no server. Execution time and trigger quotas apply and differ by account type, so check Google's quota page before you move a long job there.

## 6. A cloud platform for Claude workflows

The options above all assume you take Claude's script and become its owner: repo, secrets, monitoring, storage. [Teton](https://teton.so) takes a different approach. You do the job once with Claude in plain language until the output is right, then type `/teton` in Claude Code. Claude writes it up as one workflow file with the trigger, the steps and the credentials it needs. There is no repository to set up, and one deploy makes it live in the cloud whether your computer is on or not.

What it covers from the list of concerns above:

- Triggers can be a schedule (cron with a timezone, so no UTC conversion), a webhook, or manual.
- Each workspace has its own Postgres database for state between runs, such as snapshots or what was already sent.
- Credentials are entered once in the dashboard, never in the chat or the file, and only sent to the API hosts the workflow declares.
- Every run is listed with its result and cost, and every version is kept so you can roll back in one click.
- Steps run the same way each time, and Claude is called only where a step needs judgment.

**Good for:** GTM and RevOps jobs built with Claude that a team depends on and nobody wants to maintain. If you are happy owning a repo and the script never needs Claude again, GitHub Actions or a Worker is simpler.

## Comparison

| | Languages | Runs with laptop off | Timezone | State between runs | Main watch-out |
| --- | --- | --- | --- | --- | --- |
| Local cron | Any | No | Local | Local files | Machine must be awake |
| GitHub Actions | Any, usually Python | Yes | UTC | Commit back or external store | Delays, dropped and silently disabled runs |
| Cloudflare Workers | JS / TS | Yes | UTC | KV or D1 | CPU limit on Free, few triggers |
| Render and similar | Any | Yes | Check host | Managed database | Paid, more setup |
| Apps Script | Apps Script (JS) | Yes | Script setting | Sheets or properties | Quotas, Google-only |
| Teton | Claude workflow | Yes | Set per schedule | Postgres per workspace | Built around Claude workflows |

## Make the script ready before you deploy it

Ask Claude for these changes before moving any script off your machine:

1. Read every key from environment variables, never from the file.
2. A pinned `requirements.txt` or `package.json`.
3. A timeout on every network call, and a non-zero exit on failure.
4. Idempotency: running twice must not send twice. Store what was already sent.
5. A heartbeat ping as the last step.

The heartbeat matters most. Hosts alert you when a run fails, but [not when a run is never created](https://hyperping.com/blog/how-to-monitor-github-actions-scheduled-workflows). That page tested public workflows scheduled every 15 minutes and saw runs in only 64% of slots over a week. It is a monitoring vendor's own test, but the fix holds: ping a URL from a heartbeat monitor at the end of each successful run, and get alerted when the ping stops.

```python
import os
import requests

def run_job():
    pass  # the work Claude wrote goes here

run_job()
requests.get(os.environ["HEARTBEAT_URL"], timeout=10)
```

If `run_job()` raises, the script exits before the ping, and the monitor alerts you.

## FAQ

### How do I handle daylight saving time on hosts that use UTC?

A fixed UTC cron drifts by an hour twice a year. Either accept the shift, or schedule both candidate UTC hours and have the script exit unless local time matches, for example with Python's `zoneinfo`.

### Is a public GitHub repo safe for these scripts?

Repository secrets aren't visible in the code, but the code, the schedule and anything your script prints to the logs are. Keep the repo private if the logic or the output names customers or deals.

### Where should a scheduled script keep data between runs?

Not on the runner, which is wiped after each run. Use a small database, Cloudflare KV or D1, a Google Sheet, or a file committed back to the repo for tiny state.

### Can Claude Code itself run on a schedule?

Yes, in several ways. See [how to run Claude Code on a schedule](/guides/run-claude-code-on-a-schedule).

---

- [All guides](https://teton.so/guides)
- [Teton](https://teton.so): the cloud for Claude Code. Build a workflow with Claude once and Teton runs it on a schedule or a webhook.
