tinyhumansai/openhuman · error

unsupported delay unit '{unit}', use s/m/h/d

Error message

unsupported delay unit '{unit}', use s/m/h/d

What it means

parse_human_delay accepts exactly the units s, m, h and d (seconds/minutes/hours/days), defaulting to minutes when no suffix is given. Any other suffix — ms, w, min, sec — bails with this message. Decimals also fail: the split takes the leading digit run, so '1.5h' leaves unit '.5h'.

Source

Thrown at src/openhuman/cron/ops.rs:147

/// Parse a human-friendly delay string (e.g. "5m", "2h", "30s") into a
/// `chrono::Duration`. Defaults to minutes when no unit is given.
pub fn parse_human_delay(input: &str) -> Result<chrono::Duration> {
    let input = input.trim();
    if input.is_empty() {
        anyhow::bail!("delay must not be empty");
    }
    let split = input
        .find(|c: char| !c.is_ascii_digit())
        .unwrap_or(input.len());
    let (num, unit) = input.split_at(split);
    let amount: i64 = num.parse()?;
    let unit = if unit.is_empty() { "m" } else { unit };
    let duration = match unit {
        "s" => chrono::Duration::seconds(amount),
        "m" => chrono::Duration::minutes(amount),
        "h" => chrono::Duration::hours(amount),
        "d" => chrono::Duration::days(amount),
        _ => anyhow::bail!("unsupported delay unit '{unit}', use s/m/h/d"),
    };
    Ok(duration)
}

pub async fn cron_list(config: &Config) -> Result<RpcOutcome<Vec<CronJob>>, String> {
    if !config.cron.enabled {
        return Err("cron is disabled by config (cron.enabled=false)".to_string());
    }
    let jobs = cron::list_jobs(config).map_err(|e| e.to_string())?;
    Ok(RpcOutcome::single_log(jobs, "cron jobs listed"))
}

pub async fn cron_update(
    config: &Config,
    job_id: &str,
    patch: CronJobPatch,
) -> Result<RpcOutcome<CronJob>, String> {
    if job_id.trim().is_empty() {

View on GitHub (pinned to 7491200858)

Solutions

  1. Rewrite the value with a supported unit: 500ms → 1s, 2w → 14d, 10min → 10m
  2. Compound values are not supported: 1h30m → 90m
  3. A bare number means minutes — append s/h/d explicitly when you mean anything else

Example fix

# before
--delay 500ms

# after
--delay 1s
Defensive patterns

Strategy: validation

Validate before calling

fn valid_delay(s: &str) -> bool {
    let t = s.trim();
    let split = t.find(|c: char| !c.is_ascii_digit()).unwrap_or(t.len());
    let (num, unit) = t.split_at(split);
    !num.is_empty()
        && matches!(if unit.is_empty() { "m" } else { unit }, "s" | "m" | "h" | "d")
}

Type guard

const DELAY_RE = /^(\d+)(s|m|h|d)?$/;
function isValidDelay(input: string): boolean {
  return DELAY_RE.test(input.trim());
}

Prevention

When it happens

Trigger: Passing 500ms (milliseconds are unsupported — whole-second granularity), 2w, 10min, 1.5h, 90sec, or wrong-case suffixes like 5M; any free-text delay field that assumes Go/ISO-8601 duration syntax.

Common situations: Assuming PT30M / 30min / 1h30m style durations are accepted; converting timeouts from millisecond-based tools without rescaling; UI free-text inputs for delays.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/fd7453d95a04dc6b. Report an issue: GitHub.