Hmbown/CodeWhale · error · anyhow::Error
{} could not be parsed safely
Error message
{} could not be parsed safely What it means
Codewhale parses the validated .env text with dotenvy::from_read_iter and collects every entry into a Vec. If dotenvy's parser rejects any line, the whole load aborts with '{path} could not be parsed safely'. The all-or-nothing collect is deliberate: a half-loaded credential set is worse than none.
Source
Thrown at crates/tui/src/lib.rs:2496
fn load_workspace_dotenv_credentials_from_path(path: &Path) -> Result<WorkspaceDotenvReport> {
let contents = read_stable_workspace_dotenv(path)?;
let text = std::str::from_utf8(&contents)
.map_err(|_| anyhow!("{} is not valid UTF-8", path.display()))?;
if dotenv_has_variable_expansion(text) {
bail!(
"{} uses variable expansion; workspace .env values must be literal to prevent ambient-secret substitution",
path.display()
);
}
let mut report = WorkspaceDotenvReport {
path: path.to_path_buf(),
..WorkspaceDotenvReport::default()
};
let entries = dotenvy::from_read_iter(std::io::Cursor::new(contents))
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(|_| anyhow!("{} could not be parsed safely", path.display()))?;
for entry in entries {
let (key, value) = entry;
if !is_workspace_dotenv_credential_key(&key) {
report.ignored.insert(key);
continue;
}
if std::env::var_os(&key).is_some() {
continue;
}
// SAFETY: this loader runs synchronously in `main` before the runtime
// owner or Tokio workers are spawned. No concurrent environment reader
// exists inside Codewhale, and later startup code treats this process
// environment as immutable.
unsafe { std::env::set_var(&key, value) };
report.loaded.insert(key);
}
Ok(report)View on GitHub (pinned to 8880682c63)
Solutions
- Check every line for balanced quotes and a single KEY=VALUE shape; wrap values containing spaces or # in double quotes on one line
- Keep PEM keys and long tokens on a single line, or store a file path instead of the literal secret
- Remove stray characters introduced by copy-paste (smart quotes, line-break artifacts)
- Lint the file by parsing each non-comment line as KEY=VALUE before rerunning
Example fix
# before PRIVATE_KEY="-----BEGIN KEY----- abc123 -----END KEY-----" # after PRIVATE_KEY_PATH=/etc/codewhale/key.pem
Defensive patterns
Strategy: validation
Validate before calling
for (i, line) in text.lines().enumerate() {
let l = line.trim();
if l.is_empty() || l.starts_with('#') {
continue;
}
if !l.contains('=') {
eprintln!("line {} has no '='", i + 1);
}
if let Some((_k, v)) = l.split_once('=') {
if v.matches('"').count() % 2 == 1 {
eprintln!("line {} has unbalanced quotes", i + 1);
}
}
} Prevention
- Keep one KEY=VALUE pair per line
- Quote values containing spaces or # characters
- Store long secrets in files and reference the path instead of embedding them
When it happens
Trigger: A line dotenvy cannot parse: an unterminated quote (KEY="value), invalid characters in a key position, malformed export syntax, or control characters that survived the UTF-8 check.
Common situations: Hand-edited .env with a missing closing quote; a multi-line PEM key pasted without single-line quoting; a copy-paste that split or merged lines; stray full-width or invisible characters.
Related errors
- could not inspect {}: {error}
- {} is not valid UTF-8
- could not securely open {}: {error}
- could not parse match block after {signature!r}
- invalid JSON payload at key {MCP_SERVER_DEFINITIONS_KEY}; co
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/dc1db6d19fac6888.
Report an issue: GitHub.