Stop OpenClaw's 30-minute heartbeat from running up your API bill

What OpenClaw's heartbeat does every 30 minutes, what it costs on a shared session, and the settings that cut it to a few dollars a month or turn it off.

· ranx.cloud

Illustration of a mantel clock with a swinging pendulum next to a jar slowly leaking coins

OpenClaw wakes your agent every 30 minutes by default, whether or not anything happened. On a long-running main session each of those wakes can send the whole conversation to the model again. This post explains what the heartbeat does, puts numbers on it, and gives the settings that make it cheap, quiet outside your waking hours, or off, with scheduled Automations covering the jobs you actually want at fixed times.

Everything below is done in the Control UI. Settings were checked against the OpenClaw 2026.11.1 heartbeat docs.

What the heartbeat does

The heartbeat is a periodic agent turn in the agent's main session. The default prompt tells the model to follow the heartbeat's checklist if there is one, not to dig up old tasks from earlier chats, and to reply NO_REPLY when nothing needs attention. A quiet reply is dropped, so most heartbeats produce no message at all. You still pay for them.

The defaults that matter:

Setting Default
every 30m (1h when Anthropic is used through OAuth or token auth)
isolatedSession false: the run happens in your main session, with its history
lightContext false: workspace bootstrap files are loaded
target owner: alerts go to the operator's DM, never to a group
activeHours unset: runs around the clock

Two details from the docs explain when it runs at all:

  • Alerts need somewhere to go. The owner target uses the first entry in commands.ownerAllowFrom, then the channel's allowFrom. If neither resolves to a DM, scheduled polls are skipped with reason=no-route. That's why some fresh installs never seem to heartbeat and then start once a messaging channel is connected.
  • Each heartbeat has a private checklist (the docs call it monitor scratch). If a checklist exists but is effectively empty, the run is skipped without a model call. If no checklist exists, the heartbeat runs and the model decides what to do.

What it costs

48 runs a day is the number to keep in mind. The docs put a heartbeat that carries the full conversation history at around 100K tokens, and an isolated one at around 2K to 5K. With Claude Sonnet 5.5 at $2 per million input tokens (Anthropic pricing, November 2026):

default (main session, ~100K tokens per run):
  48 × 100,000 = 4,800,000 input tokens/day
  4.8M × $2 / 1e6 = $9.60/day  ≈ $288/month before caching

isolatedSession + lightContext (~5K tokens per run):
  48 × 5,000 = 240,000 input tokens/day
  0.24M × $2 / 1e6 = $0.48/day ≈ $14/month

same, every 2h during 08:00–22:00 (7 runs):
  7 × 5,000 × $2 / 1e6 = $0.07/day ≈ $2.10/month

Prompt caching lowers the first figure a lot when the history barely changes between runs (cache reads are $0.10 per million on Sonnet 5.5), and 100K is the size of a long session, not a new one. Treat $288 as the ceiling. Still, an idle agent that costs real money every day is a common complaint: one user reported about 2,500 credits a day spent on an idle heartbeat.

The Control UI's Usage page shows what you actually spend. Look at it after a day with the defaults if you want a baseline before changing anything.

Make it cheap

Open Settings → Advanced, switch to Raw, and merge this in:

{
  commands: {
    ownerAllowFrom: ["whatsapp:+15551234567"],  // where heartbeat alerts go
  },
  agents: {
    defaults: {
      heartbeat: {
        every: "2h",
        isolatedSession: true,   // fresh session per run, no conversation history
        lightContext: true,      // skip workspace bootstrap files
        activeHours: { start: "08:00", end: "22:00", timezone: "Europe/Berlin" },
      },
    },
  },
}

What each line buys you:

  • isolatedSession: true is the big one. Each run starts a fresh session, so it no longer resends your conversation. Delivery still goes to the same place.
  • lightContext: true skips the workspace bootstrap files for heartbeat runs. The checklist is still injected.
  • activeHours skips runs outside the window, in the given timezone. Without a timezone it uses your userTimezone if set, otherwise the host's, which on a server is usually UTC.
  • every: "2h" is a judgment call. If nothing you rely on needs a 30-minute reaction time, there's no reason to pay for one.

The heartbeat can also use its own model: set model inside the heartbeat block, in provider/model form. If you do, keep isolatedSession: true. The docs warn that otherwise the smaller model can carry over into your next main-session turn and overflow its context window.

Or turn it off

{ agents: { defaults: { heartbeat: { every: "0m" } } } }

0m stops the recurring cadence. Event-driven wakes still work: when a background command the agent started finishes, it gets one turn to report back. If you want to stop those too, set tools.exec.notifyOnExit: false.

Turning it off is reasonable if you only use OpenClaw when you message it, and you put anything time-based into Automations, which is the next section.

Put fixed-time work in Automations instead

The heartbeat is the wrong tool for "do X every weekday at 17:30". The docs say so directly: recurring checks belong in automation jobs, each with its own schedule, run history and on/off switch. An automation can run in an isolated session, so you pay for one small run at the time you chose instead of 48 speculative ones.

An example: an end-of-day follow-up list. Open Automations in the sidebar, start a New automation, and fill it in:

Field Value
Name Open follow-ups
Prompt see below
Schedule Cron, 30 17 * * 1-5 (weekdays 17:30)
Timezone your IANA zone, for example Europe/Berlin
Session Isolated session
Delivery → Mode Announce summary
Channel whatsapp or discord
To your number (+15551234567) for WhatsApp, or channel:<channelId> for a Discord channel
Use your session tools to look through today's conversations for things I
asked you to do or to remind me about. List the ones that are still open, at most five, one line each, with
the session they came from. If nothing is open, say "Nothing open" and stop.

New isolated automations deliver internally by default, so pick Announce summary or nothing reaches your phone. Click Create & run now to test it, then check Run history on the job's page: it shows whether the run succeeded and whether delivery happened.

Under Advanced, the Model field lets one job use a lighter model than your default. A list like this doesn't need your best model.

Once you're a command owner on a channel, you can manage automations by messaging the bot: "move my follow-ups to 18:00", "pause the follow-ups until Monday", "run it now" (conversational management). Senders who are only on the DM allowlist can't.

Cost of that job on Sonnet 5.5, assuming about 15,000 input tokens (system prompt, tools, a look through today's sessions) and 400 output tokens:

15,000 × $2  / 1e6 = $0.030
   400 × $10 / 1e6 = $0.004
≈ $0.034 per run × 22 weekdays ≈ $0.75/month

What goes wrong

Heartbeats never run. No owner route. Set commands.ownerAllowFrom to a concrete entry such as "whatsapp:+15551234567" or "discord:<your user id>". A wildcard-only allowlist doesn't identify an owner.

Heartbeat alerts land in the wrong place. target is owner by default and never posts to a group. If you changed it to last, it follows your most recent conversation, groups included. Set it back to owner, or to none for runs that should never message you.

Isolated runs still seem to grow. Older versions had bugs where isolatedSession: true reused the same transcript (#64795, #70173). Both are closed. If the context meter on heartbeat runs keeps climbing, check the session's Details → Context panel and your version.

An automation ran but nothing arrived. Delivery is still internal-only, or the target is malformed. WhatsApp wants an E.164 number; Discord wants channel:<id> or user:<id>, and bare numeric IDs are rejected. Run history shows the delivery status and any suppression reason.

It fires at the wrong hour. No timezone on the job, so it used the host's clock. Edit the job and set Timezone.

Owner commands from WhatsApp are refused. Being on allowFrom lets you chat; it doesn't make you an owner. Add yourself to commands.ownerAllowFrom with the channel prefix.

Restarts at the wrong moment. An instance on ranx.cloud can be restarted briefly now and then, for example for maintenance or updates. OpenClaw reschedules an overdue automation after startup instead of dropping it, so a job whose slot fell inside a restart may arrive late. If you'd rather skip a missed run than get a stale one, set cron.skipMissedJobs: true.