astrid-runtime/astrid · error

gateway.idle_shutdown_secs must be positive and representabl

Error message

gateway.idle_shutdown_secs must be positive and representable

What it means

run() validates the configured gateway.idle_shutdown_secs before starting the gateway. The value must be strictly positive (zero disables the idle-shutdown grace in an invalid way here) and must fit into an Instant offset (checked_add must not overflow). Otherwise the gateway refuses to start with this bail.

Source

Thrown at crates/astrid-cli/src/commands/mcp/gateway.rs:478

        boot_token: boot_token.clone(),
        armed: true,
    };

    // The gateway's authenticated uplinks keep an automatically started daemon
    // alive. Releasing them after the final host disconnects lets it retire.
    // An existing operator-started daemon retains its chosen lifetime.
    crate::commands::daemon::ensure_daemon("mcp-gateway")
        .await
        .context("failed to ensure Astrid daemon for MCP gateway")?;

    let idle_grace = Duration::from_secs(
        astrid_config::Config::load(Some(&daemon_root))?
            .config
            .gateway
            .idle_shutdown_secs,
    );
    if idle_grace.is_zero() || Instant::now().checked_add(idle_grace).is_none() {
        anyhow::bail!("gateway.idle_shutdown_secs must be positive and representable");
    }

    let hook_token = mint_hook_token();
    let state = Arc::new(GatewayState::new(
        daemon_root,
        caller.clone(),
        hook_token.clone(),
    ));
    // Warm the authenticated principal selected by `ready`/`gateway` before
    // accepting any attach registration. The lease was published first so this
    // slow, pre-listener generation remains authenticated-stop capable.
    state.client_for(&caller).await?;
    let socket_path = prepare_gateway_socket(&lifecycle).await?;
    let listener = UnixListener::bind(&socket_path)
        .with_context(|| format!("failed to bind MCP gateway at {}", socket_path.display()))?;
    set_socket_mode(&socket_path)?;
    let ready = GatewayReady {
        version: 1,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Set gateway.idle_shutdown_secs in your astrid config to a positive value (e.g. 30 or 60).
  2. Do not use 0 to disable idle shutdown; use the config mechanism intended for disabling it, or a large-but-representable duration.
  3. Clamp the configured value in code/config to a sane range such as 1..=86400 before launching the gateway.
  4. Reload/re-verify the config after edits: astrid_config::Config::load(Some(&daemon_root)) picks up the file at daemon_root.

Example fix

// before (config)
[gateway]
idle_shutdown_secs = 0
// after
[gateway]
idle_shutdown_secs = 60
Defensive patterns

Strategy: validation

Validate before calling

fn valid_idle_secs(v: i64) -> bool { v > 0 && v <= 86_400 }

Type guard

fn as_positive_secs(raw: &str) -> Option<u64> {
    raw.trim().parse::<u64>().ok().filter(|&s| s > 0)
}

Try / catch

match result {
    Err(e) if e.to_string().contains("idle_shutdown_secs") => eprintln!("fix [gateway].idle_shutdown_secs in your astrid config: must be > 0"),
    other => other?,
}

Prevention

When it happens

Trigger: Setting gateway.idle_shutdown_secs to 0, to a negative value, or to a magnitude so large that Instant::now() + idle_grace overflows when loading astrid_config::Config in `run`.

Common situations: Typo or misunderstanding in the config file (e.g. idle_shutdown_secs: 0 thinking it means 'never idle-shutdown'); hand-editing the config with an absurdly large number of seconds; generating the config programmatically with an out-of-range integer.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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