Scheduled Jobs on Tawa
Declare schedules in `catalog-info.yaml`. iec-cron POSTs to your endpoint on schedule. Return 200 immediately — do slow work asynchronously.
Ruleconfigure
About
Declare schedules in `catalog-info.yaml`. iec-cron POSTs to your endpoint on schedule. Return 200 immediately — do slow work asynchronously.
Skill Content
This is the raw markdown that gets installed as a Claude Code rule.
# Scheduled Jobs on Tawa
## The Short Version
Declare schedules in `catalog-info.yaml`. iec-cron POSTs to your endpoint on schedule. Return 200 immediately — do slow work asynchronously.
```yaml
spec:
schedules:
- name: nightly-sync
cron: "0 2 * * *"
endpoint: /internal/cron/nightly-sync
timezone: America/Denver
timeoutMs: 300000
```
## Handler Pattern
```typescript
// Return 200 immediately — iec-cron must not wait for slow work
app.post('/internal/cron/nightly-sync', async (req, res) => {
res.json({ success: true })
// Do work asynchronously after responding
doSlowWork().catch((err) => logger.error({ err }, 'Nightly sync failed'))
})
// For fast work (< timeoutMs), you can do it inline:
app.post('/internal/cron/hourly-check', async (req, res) => {
await runQuickCheck()
res.json({ success: true })
})
```
## How iec-cron Calls Your Service
iec-cron POSTs the callback through Janus at `{JANUS_URL}/i/{serviceName}{endpoint}`, where
`serviceName` is your namespace with the environment suffix stripped (e.g. `my-svc-prod` →
`/i/my-svc/internal/cron/...`). The service JWT is passed as `Authorization: Bearer`.
```
POST {JANUS_URL}/i/{serviceName}{endpoint}
Authorization: Bearer {service-jwt}
X-Schedule-Name: nightly-sync
X-Cron-Expression: 0 2 * * *
X-Fired-At: 2026-01-15T07:00:00.000Z
```
## Cron Syntax
```
┌──────── minute (0-59)
│ ┌────── hour (0-23)
│ │ ┌──── day of month (1-31)
│ │ │ ┌── month (1-12)
│ │ │ │ ┌ day of week (0-7, both 0 and 7 = Sunday)
│ │ │ │ │
"*/15 * * * *" every 15 minutes
"0 * * * *" top of every hour
"0 0 * * *" daily at midnight UTC
"0 9 * * 1-5" weekdays at 9am
"0 2 1 * *" 1st of each month at 2am
```
Only 5-part cron expressions. 6-part (with seconds) is NOT supported.
## Schedule Fields
| Field | Required | Default | Description |
|-------|----------|---------|-------------|
| `name` | Yes | — | Unique name in your namespace |
| `cron` | Yes | — | 5-part cron expression |
| `endpoint` | Yes | — | Path in your service |
| `timezone` | No | UTC | IANA timezone string |
| `timeoutMs` | No | 30000 | HTTP callback timeout |
| `description` | No | — | Human description |
## Gas
| Environment | Cost per execution |
|-------------|-------------------|
| Production | 5 tokens |
| Sandbox | 0 tokens (free) |
Gas is charged upfront when the schedule fires, before your callback is invoked. Sandbox
suppresses fires entirely and charges no gas.
## What NOT to Do
```yaml
# ❌ WRONG: cron endpoints listed in routes (they must be internal-only)
spec:
routes:
- path: /internal/cron/nightly-sync # now exposed through Janus
# ✅ CORRECT: cron endpoints only in schedules
spec:
schedules:
- name: nightly-sync
endpoint: /internal/cron/nightly-sync
# ❌ WRONG: /api/ prefix on cron endpoints
endpoint: /api/cron/nightly-sync # suggests it's a public API route
# ✅ CORRECT
endpoint: /internal/cron/nightly-sync
# ❌ WRONG: 6-part cron (with seconds)
cron: "0 0 2 * * *" # second field not supported
# ✅ CORRECT
cron: "0 2 * * *"
```
```typescript
// ❌ WRONG: blocking the response with slow work
app.post('/internal/cron/nightly-sync', async (req, res) => {
await doSlowWork() // iec-cron waits, times out, marks failure
res.json({ ok: true })
})
// ✅ CORRECT: respond immediately
app.post('/internal/cron/nightly-sync', async (req, res) => {
res.json({ ok: true })
doSlowWork().catch(logger.error)
})
```
## Key Facts
- iec-cron polls Koko every 30 seconds — new schedules go live within 30 seconds of deploy
- Schedule names must be unique within your namespace
- Non-2xx response or timeout = recorded as failure in run history
- Run history viewable in tawa-web: Console → Infrastructure → Schedules
- Septor events are written automatically for every cron execution (platform handles this)
Install
Copy the skill content and save it to:
~/.claude/rules/tawa-cron.mdComing soon via CLI:
tawa chaac install tawa-cronDetails
- Format
- Rule
- Category
- configure
- Version
- 1.0.63884
- Tokens
- ~969
- Updated
- 2026-06-24
platformcronschedulesscheduleriec-cronrecurring