astrid-runtime/astrid · error

failed to spawn hook epoch ticker

Error message

failed to spawn hook epoch ticker

What it means

`WasmHandler::new` spawns a background OS thread that sleeps 100ms at a time and calls `ticker_engine.increment_epoch()` to power epoch-based fuel/interruption of hook execution. It panics if `std::thread::Builder::spawn` fails (thread creation returned an error), because hooks would otherwise never be preempted.

Solutions

  1. Raise the thread/pid limits (`ulimit -u`, cgroup pids.max) in the deployment environment.
  2. Reduce concurrent handler/thread count so spawn succeeds.
  3. Return a `Result` from `new` instead of panicking, mapping the io::Error to a hook-init error.
  4. Consider a lazily started singleton ticker thread shared by all handlers to avoid per-handler spawns.

Example fix

// before
.spawn(move || { /* epoch loop */ })
.expect("failed to spawn hook epoch ticker");
// after
.spawn(move || { /* epoch loop */ })
    .map_err(|e| HookError::TickerSpawn(e.to_string()))?
Defensive patterns

Strategy: fallback

Validate before calling

// Rust: pre-check headroom before spawning the ticker
fn can_spawn_thread() -> bool {
    std::thread::Builder::new().stack_size(64 * 1024).spawn(|| {}).map(|h| h.join().is_ok()).unwrap_or(false)
}

Try / catch

// Rust: fail construction with context instead of panicking
let ticker = builder.spawn(loop_body)
    .map_err(|e| HookError::TickerSpawn(e.to_string()))?;

Prevention

When it happens

Trigger: Calling `WasmHandler::new` when `thread::spawn` fails: OS resource exhaustion (hit pid/thread limits like `ulimit -u`, cgroup `pids.max`, or `EAGAIN` from `pthread_create`), or insufficient memory for a new thread stack.

Common situations: Running many handlers/threads in constrained containers with low `pids.max`, deep recursion elsewhere exhausting stack, or a process approaching its thread limit under load.

Understand the failure class

Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/fac34b0b355f5484. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-hooks/src/handler/wasm.rs:126

impl WasmHandler {
    /// Create a new WASM handler.
    #[must_use]
    pub(crate) fn new(workspace_root: PathBuf) -> Self {
        let engine = build_hook_engine();

        // Spawn epoch ticker so that epoch deadlines on Store actually fire.
        let epoch_stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
        let stop_clone = epoch_stop.clone();
        let ticker_engine = engine.clone();
        let epoch_handle = std::thread::Builder::new()
            .name("hook-epoch-ticker".into())
            .spawn(move || {
                while !stop_clone.load(std::sync::atomic::Ordering::Relaxed) {
                    std::thread::sleep(Duration::from_millis(100));
                    ticker_engine.increment_epoch();
                }
            })
            .expect("failed to spawn hook epoch ticker");

        Self {
            engine,
            cached_components: Mutex::new(HashMap::new()),
            config: WasmConfig::default(),
            kv: None,
            http_limits: resolve_http_limits(),
            workspace_root,
            epoch_stop,
            epoch_handle: Some(epoch_handle),
        }
    }

    /// Set the KV store for hook state persistence.
    #[must_use]
    pub(crate) fn with_kv(mut self, kv: ScopedKvStore) -> Self {
        self.kv = Some(kv);
        self

View on GitHub (pinned to affd8760f4)