{"record":{"id":"7992441cba790a83","repo":"Hmbown/CodeWhale","slug":"sub-agent-state-path-must-be-a-regular-file","errorCode":null,"errorMessage":"sub-agent state path must be a regular file: {}","messagePattern":"sub-agent state path must be a regular file: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/mod.rs","lineNumber":8918,"sourceCode":"                \"sub-agent state path must not traverse symlinks: {}\",\n                current.display()\n            ));\n        }\n    }\n    Ok(())\n}\n\n/// Maximum bytes read from a sub-agent state or transcript artifact file.\n/// Generous for long session histories; anything larger is corrupt.\nconst MAX_SUBAGENT_STATE_BYTES: u64 = 16 * 1024 * 1024;\n\nfn read_subagent_state_file(state_root: &Path, path: &Path) -> Result<String> {\n    let state_root = normalize_subagent_workspace(state_root);\n    reject_root_relative_symlinks(&state_root, path)?;\n    let metadata = fs::symlink_metadata(path)?;\n    let file_type = metadata.file_type();\n    if file_type.is_symlink() || !file_type.is_file() {\n        return Err(anyhow!(\n            \"sub-agent state path must be a regular file: {}\",\n            path.display()\n        ));\n    }\n\n    let file = open_subagent_state_file(path)?;\n    let mut raw = String::new();\n    file.take(MAX_SUBAGENT_STATE_BYTES + 1)\n        .read_to_string(&mut raw)?;\n    if raw.len() as u64 > MAX_SUBAGENT_STATE_BYTES {\n        return Err(anyhow!(\n            \"sub-agent state file {} exceeds the 16 MiB limit\",\n            path.display()\n        ));\n    }\n    Ok(raw)\n}\n","sourceCodeStart":8900,"sourceCodeEnd":8936,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/subagent/mod.rs#L8900-L8936","documentation":"When reading a sub-agent state file, the library takes `fs::symlink_metadata` and requires the entry to be a regular file; symlinks, directories, and special files are rejected even if reading through them would succeed. This guarantees state contents come from the real file inside the state root, not an alias.","triggerScenarios":"Calling a sub-agent state read API where the target path is a symlink, a directory, or a special file — `file_type().is_symlink() || !file_type().is_file()`.","commonSituations":"The state file was replaced by a symlink to a shared/dotfile-managed location; a directory was created at the expected file path; a FIFO or device node occupies the path in a container.","solutions":["Replace the symlink/directory with a regular file containing the state (copy the target's contents).","Delete the unexpected entry and let the library recreate the state file.","Configure dotfile managers to copy rather than symlink files under the state root.","Verify container/volume setups so the state path is a plain file."],"exampleFix":"// before: state file is a symlink\nstate/session.json -> /mnt/shared/session.json\n// after\nrm state/session.json\ncp /mnt/shared/session.json state/session.json","handlingStrategy":"validation","validationCode":"use std::fs;\nfn readable_regular_state_file(p: &std::path::Path) -> bool {\n    fs::symlink_metadata(p).map(|m| m.file_type().is_file()).unwrap_or(false)\n}\n// guard before reading: readable_regular_state_file(&state_path)","typeGuard":null,"tryCatchPattern":"match read_state(root, path) {\n    Ok(contents) => contents,\n    Err(e) if e.to_string().contains(\"must be a regular file\") => {\n        // entry is a symlink/dir: restore a real file or accept defaults\n        Default::default()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never symlink individual state files (dotfile managers: use copy mode).","Verify a directory was not created where the state file belongs.","Restore state files as regular files after backup/restore operations."],"tags":["subagent","state","symlink","validation"],"backgroundTag":"incompatible-source-type","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}