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

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
ownertarget uses the first entry incommands.ownerAllowFrom, then the channel'sallowFrom. If neither resolves to a DM, scheduled polls are skipped withreason=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: trueis the big one. Each run starts a fresh session, so it no longer resends your conversation. Delivery still goes to the same place.lightContext: trueskips the workspace bootstrap files for heartbeat runs. The checklist is still injected.activeHoursskips runs outside the window, in the given timezone. Without a timezone it uses youruserTimezoneif 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.
Related
- Connect the channel the alerts go to: WhatsApp or Discord.
- Before the agent posts into shared rooms, read the group-chat safety guide.


