Skip to content
Back to skills

434 Gotchas Ca32b16d

ASecurity

**Problem:** Cron runs at wrong time relative to local timezone **Cause:** All crons execute in UTC, no local timezone support **Solution:** Convert local time to UTC manually

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
testingtypescriptgobashexpresstestinggitapisecurity

Works with

  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 434-gotchas_ca32b16d --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 434 Gotchas Ca32b16d?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 434 Gotchas Ca32b16d
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-434-gotchas-ca32b16d/badge)](https://www.skillsdirectory.com/skills/tools-only-434-gotchas-ca32b16d)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Cron Triggers Gotchas

## Common Errors

### "Timezone Issues"

**Problem:** Cron runs at wrong time relative to local timezone  
**Cause:** All crons execute in UTC, no local timezone support  
**Solution:** Convert local time to UTC manually

**Conversion formula:** `utcHour = (localHour - utcOffset + 24) % 24`

**Examples:**

- 9am PST (UTC-8) → `(9 - (-8) + 24) % 24 = 17` → `0 17 * * *`
- 2am EST (UTC-5) → `(2 - (-5) + 24) % 24 = 7` → `0 7 * * *`
- 6pm JST (UTC+9) → `(18 - 9 + 24) % 24 = 33 % 24 = 9` → `0 9 * * *`

**Daylight Saving Time:** Adjust manually when DST changes, or schedule at times unaffected by DST (e.g., 2am-4am local time usually safe)

### "Cron Not Executing"

**Cause:** Missing `scheduled()` export, invalid syntax, propagation delay (<15min), or plan limits  
**Solution:** Verify export exists, validate at crontab.guru, wait 15+ min after deploy, check plan limits

### "Duplicate Executions"

**Cause:** At-least-once delivery  
**Solution:** Track execution IDs in KV - see idempotency pattern below

### "Execution Failures"

**Cause:** CPU exceeded, unhandled exceptions, network timeouts, binding errors  
**Solution:** Use try-catch, AbortController timeouts, `ctx.waitUntil()` for long ops, or Workflows for heavy tasks

### "Local Testing Not Working"

**Problem:** `/__scheduled` endpoint returns 404 or doesn't trigger handler  
**Cause:** Missing `scheduled()` export, wrangler not running, or incorrect endpoint format  
**Solution:**

1. Verify `scheduled()` is exported:

```typescript
export default {
  async scheduled(controller, env, ctx) {
    console.log('Cron triggered')
  },
}
```

2. Start dev server:

```bash
npx wrangler dev
```

3. Use correct endpoint format (URL-encode spaces as `+`):

```bash
# Correct
curl "http://localhost:8787/__scheduled?cron=*/5+*+*+*+*"

# Wrong (will fail)
curl "http://localhost:8787/__scheduled?cron=*/5 * * * *"
```

4. Update Wrangler if outdated:

```bash
npm install -g wrangler@latest
```

### "waitUntil() Tasks Not Completing"

**Problem:** Background tasks in `ctx.waitUntil()` fail silently or don't execute  
**Cause:** Promises rejected without error handling, or handler returns before promise settles  
**Solution:** Always await or handle errors in waitUntil promises:

```typescript
export default {
  async scheduled(controller, env, ctx) {
    // BAD: Silent failures
    ctx.waitUntil(riskyOperation())

    // GOOD: Explicit error handling
    ctx.waitUntil(
      riskyOperation().catch((err) => {
        console.error('Background task failed:', err)
        return logError(err, env)
      }),
    )
  },
}
```

### "Idempotency Issues"

**Problem:** At-least-once delivery causes duplicate side effects (double charges, duplicate emails)  
**Cause:** No deduplication mechanism  
**Solution:** Use KV to track execution IDs:

```typescript
export default {
  async scheduled(controller, env, ctx) {
    const executionId = `${controller.cron}-${controller.scheduledTime}`
    const existing = await env.EXECUTIONS.get(executionId)

    if (existing) {
      console.log('Already executed, skipping')
      controller.noRetry()
      return
    }

    await env.EXECUTIONS.put(executionId, '1', { expirationTtl: 86400 }) // 24h TTL
    await performIdempotentOperation(env)
  },
}
```

### "Security Concerns"

**Problem:** `__scheduled` endpoint exposed in production allows unauthorized cron triggering  
**Cause:** Testing endpoint available in deployed Workers  
**Solution:** Block `__scheduled` in production:

```typescript
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url)

    // Block __scheduled in production
    if (url.pathname === '/__scheduled' && env.ENVIRONMENT === 'production') {
      return new Response('Not Found', { status: 404 })
    }

    return handleRequest(request, env, ctx)
  },

  async scheduled(controller, env, ctx) {
    // Your cron logic
  },
}
```

**Also:** Use `env.API_KEY` for secrets (never hardcode)

**Alternative:** Add middleware to verify request origin:

```typescript
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url)

    if (url.pathname === '/__scheduled') {
      // Check Cloudflare headers to verify internal request
      const cfRay = request.headers.get('cf-ray')
      if (!cfRay && env.ENVIRONMENT === 'production') {
        return new Response('Not Found', { status: 404 })
      }
    }

    return handleRequest(request, env, ctx)
  },

  async scheduled(controller, env, ctx) {
    // Your cron logic
  },
}
```

## Limits & Quotas

| Limit               | Free             | Paid             | Notes                                    |
| ------------------- | ---------------- | ---------------- | ---------------------------------------- |
| Triggers per Worker | 3                | Unlimited        | Maximum cron schedules per Worker        |
| CPU time            | 10ms             | 50ms             | May need `ctx.waitUntil()` or Workflows  |
| Execution guarantee | At-least-once    | At-least-once    | Duplicates possible - use idempotency    |
| Propagation delay   | Up to 15 minutes | Up to 15 minutes | Time for changes to take effect globally |
| Min interval        | 1 minute         | 1 minute         | Cannot schedule more frequently          |
| Cron accuracy       | ±1 minute        | ±1 minute        | Execution may drift slightly             |

## Testing Best Practices

**Unit tests:**

- Mock `ScheduledController`, `ExecutionContext`, and bindings
- Test each cron expression separately
- Verify `noRetry()` is called when expected
- Use Vitest with `@cloudflare/vitest-pool-workers` for realistic env

**Integration tests:**

- Test via `/__scheduled` endpoint in dev environment
- Verify idempotency logic with duplicate `scheduledTime` values
- Test error handling and retry behavior

**Production:** Start with long intervals (`*/30 * * * *`), monitor Cron Events for 24h, set up alerts before reducing interval

## Resources

- [Cron Triggers Docs](https://developers.cloudflare.com/workers/configuration/cron-triggers/)
- [Scheduled Handler API](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/)
- [Cloudflare Workflows](https://developers.cloudflare.com/workflows/)
- [Workers Limits](https://developers.cloudflare.com/workers/platform/limits/)
- [Crontab Guru](https://crontab.guru/) - Validator
- [Vitest Pool Workers](https://github.com/cloudflare/workers-sdk/tree/main/fixtures/vitest-pool-workers-examples)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.