Wiki
猫Runners
Russian version: Runners-RU
Runners are background or one-time tasks that start with the bot and live alongside update processing. They are useful for periodic cleanup, maintenance jobs, health checks, and startup work that belongs to the bot process but not to any single update handler.
Overview
Runners are registered with Bot.AddRunner(...) and executed from the bot runtime entry points.
Each runner is built from:
- a name;
- a function
func(*Bot[T]) error; - execution flags configured through builder methods.
Builder methods:
Every(time.Duration)— sets the repeat interval. Zero (default) means run once; positive means repeat.Async(bool)— iftrue(default), the runner runs in a goroutine; iffalse, it blocks runtime startup.
Creating a runner
Use NewRunner(name, fn) to create a runner.
cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
return cleanupExpiredState(bot.GetAppData())
})
By default, a new runner is:
- asynchronous (
Async(true)); - one-shot (
Every(0)).
That default fires the runner once in a goroutine when the bot starts.
Runner execution modes
There are three meaningful configurations.
One-time asynchronous (default)
runner := laniakea.NewRunner("prefetch", fn)
// or explicitly:
runner := laniakea.NewRunner("prefetch", fn).Every(0).Async(true)
Behavior:
- runs once;
- starts in a goroutine;
- does not block bot startup.
Use this for fire-and-forget startup work that is useful but not required before handling updates.
One-time synchronous
runner := laniakea.NewRunner("warmup", fn).Async(false)
Behavior:
- runs once;
- blocks startup until it finishes;
- logs a warning if it takes longer than two seconds.
Use this for startup work that must complete before the bot is considered ready.
Repeating asynchronous
runner := laniakea.NewRunner("cleanup", fn).Every(time.Minute)
Behavior:
- runs on a ticker with the configured interval;
- keeps running until
ctx.Done()from the bot runtime context; - is awaited during graceful shutdown.
Use this for recurring background jobs.
Invalid configurations
One configuration is intentionally treated as invalid and skipped with a warning:
Every(d > 0).Async(false)— a periodic sync runner blocks startup indefinitely, which is never correct.
Registration
Add runners with Bot.AddRunner(...).
bot.AddRunner(cleanupRunner)
Runners with an empty name are skipped with a warning, so always give them a stable, readable name.
Lifecycle
Runners are not started by NewBot(...). They start from RunWithContext(...) or RunWebhookWithContext(...), right before the bot begins polling or webhook ingestion.
That means runner execution belongs to the bot's runtime lifecycle, not to its configuration phase.
High-level order:
- validate bot startup;
- start runners;
- begin polling or webhook ingress;
- process updates concurrently;
- cancel context to stop ingress and let runners exit.
For the broader bot runtime model, see Bot-Lifecycle.
Error handling
Runner functions return error.
When a runner returns a non-nil error:
- the bot logs a warning;
- an
ErrorEventis emitted through the observer; - the process continues;
- the bot does not crash automatically.
This is useful for periodic jobs where failure should be visible but not fatal.
If a runner must be fatal for startup, make it one-time synchronous and return an error that your process architecture handles outside the runner itself.
Shutdown behavior
RunWithContext(...) and RunWebhookWithContext(...) wait for runner completion in two groups:
- one-time async runners;
- background repeating runners.
Repeating runners observe ctx.Done() and exit when the bot context is canceled.
This means graceful shutdown includes runner shutdown, but only if your runner function itself returns promptly and does not ignore cancellation indirectly.
Practical patterns
Periodic cleanup
cleanup := laniakea.NewRunner("cleanup", func(bot *laniakea.Bot[*App]) error {
return bot.GetAppData().CleanupExpired()
}).Every(5 * time.Minute)
Startup warmup (blocking)
warmup := laniakea.NewRunner("warmup", func(bot *laniakea.Bot[*App]) error {
return bot.GetAppData().WarmCaches()
}).Async(false)
Background metrics push
metrics := laniakea.NewRunner("metrics", func(bot *laniakea.Bot[*App]) error {
return pushMetrics(bot.GetAppData())
}).Every(30 * time.Second)
Recommendations
- Use one-time sync runners only for short startup-critical work.
- Use repeating async runners for periodic jobs.
- Always set
Every(...)on repeating runners. - Keep runner bodies small and delegate complex work to regular application services.
- Treat runner names as operational identifiers that should make sense in logs.
Caveats
- Runners do not receive
context.Contextdirectly; they receive*Bot[T]. - Periodic sync runners are skipped with a warning.
- Slow one-time sync runners delay bot startup.
Related pages
Navigation
Start here
Runtime and Architecture
- Bot-Lifecycle
- Webhook-Runtime
- Middleware
- Runners
- Error-Handling
- Logging
- Update-Routing-Model
- Policies
- Scenes
Interaction and Telegram API
- Inline-Keyboards-and-Payloads
- Auto-Generated-Commands
- Drafts
- Rich-Messages
- Localization
- Rate-Limiting
- tgapi-Overview