zeroclaw-labs/zeroclaw · error · anyhow::Error
runtime.shell must not be empty or whitespace
Error message
runtime.shell must not be empty or whitespace
What it means
The unix validate_shell rejects runtime.shell values that are empty or whitespace-only, because spawning would otherwise run with no program. Note the Android carve-out just above the check: on Android the whole validation is skipped since the shell is located at spawn time.
Source
Thrown at crates/zeroclaw-config/src/platform/mod.rs:38
RuntimeKind::Docker => Ok(Box::new(DockerRuntime::new(config.docker.clone()))),
RuntimeKind::Cloudflare => anyhow::bail!(
"runtime.kind='cloudflare' is not implemented yet. Use runtime.kind='native' for now."
),
}
}
#[cfg(unix)]
fn validate_shell(shell: &str) -> anyhow::Result<()> {
use std::os::unix::fs::PermissionsExt;
// Android pins the shell to /system/bin/sh; the configured value is never
// used, so don't reject it.
if zeroclaw_api::platform::is_android() {
return Ok(());
}
if shell.trim().is_empty() {
anyhow::bail!("runtime.shell must not be empty or whitespace");
}
let path = std::path::Path::new(shell);
let resolved = if path.is_absolute() {
path.to_path_buf()
} else if path.components().count() > 1 {
anyhow::bail!(
"runtime.shell {shell:?} is a relative path; use a bare name resolved on PATH (e.g. \"bash\") or an absolute path (e.g. \"/bin/bash\")"
);
} else {
match std::env::split_paths(&std::env::var_os("PATH").unwrap_or_default())
.map(|dir| dir.join(shell))
.find(|candidate| candidate.is_file())
{
Some(found) => found,
None => anyhow::bail!(
"runtime.shell {shell:?} was not found on PATH; use an absolute path or install the shell"
),View on GitHub (pinned to 88bb9c8533)
Solutions
- Set a real shell: a bare name ("bash") or an absolute path ("/bin/bash").
- Remove the runtime.shell key to use the runtime default.
- If the value comes from env expansion, give it a fallback such as ${SHELL:-/bin/bash}.
Example fix
# before [runtime] shell = "" # after [runtime] shell = "/bin/bash"
Defensive patterns
Strategy: validation
Validate before calling
#[cfg(not(target_os = "android"))]
if cfg.runtime_shell.map_or(false, |s| s.trim().is_empty()) {
return Err(anyhow::anyhow!("runtime.shell must not be empty"));
} Type guard
fn is_nonempty_shell(value: &str) -> bool {
!value.trim().is_empty()
} Try / catch
match create_runtime(&config) {
Err(e) if e.to_string().contains("must not be empty") => {
// fill a concrete shell ("/bin/bash") or drop the key and retry
}
other => other,
} Prevention
- Omit runtime.shell rather than shipping an empty value.
- Give templated env-derived values a fallback (e.g. ${SHELL:-/bin/bash}).
- Lint generated configs for empty-string fields before deployment.
- Remember Android skips this check entirely — do not rely on it there.
When it happens
Trigger: runtime.shell = "" or " " on non-Android unix when create_runtime validates the shell before building a NativeRuntime.
Common situations: Template configs shipped with an empty shell field; templating that writes shell = "${SHELL}" with the variable unset; a truncated TOML edit leaving whitespace.
Related errors
- runtime.shell {shell:?} is a relative path; use a bare name
- runtime.kind='cloudflare' is not implemented yet. Use runtim
- runtime.shell {shell:?} was not found on PATH; use an absolu
- runtime.shell {shell:?} (resolved to {}) does not exist
- runtime.shell {shell:?} (resolved to {}) could not be inspec
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/94ba9587f14f9550.
Report an issue: GitHub.