zeroclaw-labs/zeroclaw · warning · DiagItem

gateway.web_dist_dir = "{$path}" — {$reason}; gateway.web_di

Error message

gateway.web_dist_dir = "{$path}" — {$reason}; gateway.web_dist_dir is read verbatim, so expand the value yourself (e.g. an absolute path)

What it means

A `zeroclaw doctor` warning from `check_web_dist_dir`: `gateway.web_dist_dir` starts with `~` or contains `$`, i.e. it looks like it expects shell expansion. The gateway reads the value verbatim and performs no expansion, so the dashboard static files would be served from a literally-named (nonexistent) directory. The localized message is built from the Fluent keys `cli-doctor-web-dist-dir-expansion-warning` plus a reason key (`cli-web-dist-dir-reason-tilde` or `cli-web-dist-dir-reason-dollar`).

Source

Thrown at crates/zeroclaw-runtime/src/doctor/mod.rs:1400

            cat,
            format!("{} (at {})", warning.message, warning.path),
        ));
    }
}

fn check_web_dist_dir(config: &Config, items: &mut Vec<DiagItem>) {
    let cat = "config";
    match config.gateway.web_dist_dir.as_deref() {
        None => {}
        Some(value) => match web_dist_dir_expansion_reason_key(value) {
            None => {}
            Some(reason_key) => {
                let reason = crate::i18n::get_required_cli_string(reason_key);
                let message = crate::i18n::get_required_cli_string_with_args(
                    "cli-doctor-web-dist-dir-expansion-warning",
                    &[("path", value), ("reason", reason.as_str())],
                );
                items.push(DiagItem::warn(cat, message));
            }
        },
    }
}

/// Return the Fluent reason key when `value` looks like it expects
/// shell expansion the gateway will not perform. `None` means the value
/// is a literal path that the gateway can resolve as-is.
fn web_dist_dir_expansion_reason_key(value: &str) -> Option<&'static str> {
    if value.starts_with('~') {
        Some("cli-web-dist-dir-reason-tilde")
    } else if value.contains('$') {
        Some("cli-web-dist-dir-reason-dollar")
    } else {
        None
    }
}

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Replace the value with a literal absolute path, e.g. `/home/user/zeroclaw/web/dist`.
  2. Expand the variable yourself before writing config: `zeroclaw.toml` written by a script should already contain the expanded result of `$HOME`.
  3. If the dashboard is not needed, remove `gateway.web_dist_dir` instead of leaving a broken path.
  4. Re-run `zeroclaw doctor` to confirm the warning is gone.

Example fix

# before
[gateway]
web_dist_dir = "~/zeroclaw/web/dist"

# after
[gateway]
web_dist_dir = "/home/user/zeroclaw/web/dist"
Defensive patterns

Strategy: validation

Validate before calling

if let Some(dir) = config.gateway.web_dist_dir.as_deref() {
    assert!(
        !dir.starts_with('~') && !dir.contains('$'),
        "gateway.web_dist_dir is read verbatim — expand it to a literal absolute path"
    );
}

Type guard

fn web_dist_dir_is_literal(path: &str) -> bool {
    !path.starts_with('~') && !path.contains('$')
}

Prevention

When it happens

Trigger: Running `zeroclaw doctor` when `gateway.web_dist_dir` is set to a tilde path (`~/zeroclaw/web/dist`) or contains a dollar sign (`$HOME/zeroclaw/web/dist`, `${INSTALL_DIR}/web/dist`). Detection is `value.starts_with('~')` or `value.contains('$')`.

Common situations: Porting a shell-style path from documentation or dotfiles into zeroclaw.toml; CI templates that substitute variables at write time but leave the `$` syntax when substitution is disabled.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/b0a74a8273691cf6. Report an issue: GitHub.