ultraworkers/claw-code · error · std::io::Error
credentials file must contain a JSON object
Error message
credentials file must contain a JSON object
What it means
Thrown by read_credentials_root() when ~/.claw/credentials.json exists and parses as valid JSON, but the top-level value is not an object (e.g. an array, string, or number). The credentials store is a map of provider-name to credential entries, so a non-object root is treated as corrupt data. It is returned as io::Error with ErrorKind::InvalidData, wrapping the serde_json failure only when JSON parsing itself fails; this specific message means parsing succeeded but the shape was wrong.
Source
Thrown at rust/crates/runtime/src/oauth.rs:360
"HOME is not set (on Windows, set USERPROFILE or HOME, \
or use CLAW_CONFIG_HOME to point directly at the config directory)",
)
})?;
Ok(PathBuf::from(home).join(".claw"))
}
fn read_credentials_root(path: &PathBuf) -> io::Result<Map<String, Value>> {
match fs::read_to_string(path) {
Ok(contents) => {
if contents.trim().is_empty() {
return Ok(Map::new());
}
serde_json::from_str::<Value>(&contents)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?
.as_object()
.cloned()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"credentials file must contain a JSON object",
)
})
}
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(Map::new()),
Err(error) => Err(error),
}
}
fn write_credentials_root(path: &PathBuf, root: &Map<String, Value>) -> io::Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let rendered = serde_json::to_string_pretty(&Value::Object(root.clone()))
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
let temp_path = path.with_extension("json.tmp");
fs::write(&temp_path, format!("{rendered}\n"))?;View on GitHub (pinned to 08106b0c37)
Solutions
- Fix the file so the top level is a JSON object: {"anthropic": { ... }} instead of [ ... ] or "..."
- If the file was mangled beyond repair, move it aside (mv ~/.claw/credentials.json ~/.claw/credentials.json.bak) — read_credentials_root() treats NotFound/empty as an empty map and OAuth login will recreate it
- Validate with jq before saving: jq -e 'type == "object"' ~/.claw/credentials.json
- If you generate the file programmatically, serialize a serde_json::Map<String, Value>, never a Vec or String
Example fix
// before — array root, triggers "credentials file must contain a JSON object"
[
{ "anthropic": { "access_token": "..." } }
]
// after — object root keyed by provider
{
"anthropic": { "access_token": "...", "refresh_token": "..." }
} Defensive patterns
Strategy: validation
Validate before calling
fn credentials_file_is_object(path: &std::path::Path) -> bool {
let Ok(contents) = std::fs::read_to_string(path) else { return true }; // missing/empty = OK (empty map)
serde_json::from_str::<serde_json::Value>(&contents)
.map(|v| v.is_object())
.unwrap_or(false)
}
if !credentials_file_is_object(credentials_path.as_ref()) {
return Err("credentials.json root must be a JSON object".into());
} Type guard
fn is_credentials_object(value: &serde_json::Value) -> bool {
value.is_object()
} Try / catch
match read_credentials() {
Ok(c) => c,
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
// rename the corrupt file aside; an empty map is recreated on next login
let _ = std::fs::rename(&creds_path, creds_path.with_extension("json.bak"));
serde_json::Map::new()
}
Err(e) => return Err(e.into()),
} Prevention
- Never hand-edit credentials.json into arrays or wrapped strings — the root must be { "provider": { ... } }
- Validate provisioning scripts with jq -e 'type == "object"' before writing the file
- Treat InvalidData from the credentials path as corrupt-file signal: back it up and re-login rather than retrying
When it happens
Trigger: Calling any credentials read/modify path (oauth.rs:271, 285, 296 — load, store, update) after the credentials file was hand-edited or written by another tool. Examples: someone pasted an array of tokens ([{"anthropic": ...}]) instead of an object, an editor truncated/rewrote the file, or a JSON-lines dump was saved over credentials.json.
Common situations: Manually provisioning OAuth tokens in CI by scripting credentials.json with the wrong top-level shape; a previous version of the tool or a different machine writing a different schema; file synced from a notes app or template that wrapped it in an array; partial manual migration from another credential store.
Related errors
- HOME is not set (on Windows, set USERPROFILE or HOME, or use
- session file was removed during save (possible concurrent mo
- session file was removed during write (possible concurrent m
- missing_flag_value: missing value for --cwd. Usage: --cwd <p
- PowerShell executable not found (expected `pwsh` or `powersh
AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18).
Data as JSON: /api/errors/71e3d0dad109d7cf.
Report an issue: GitHub.