tinyhumansai/openhuman · error
workspace lock file {lock_path:?} is a symlink; refusing to
Error message
workspace lock file {lock_path:?} is a symlink; refusing to follow it What it means
Security guard in the MEMORY.md cross-process lock acquisition: the sentinel .memory-write.lock file in the workspace is a symbolic link. Because flock would follow it, the tool refuses rather than lock an attacker-chosen target — a deliberate TOCTOU mitigation.
Source
Thrown at src/openhuman/tools/impl/filesystem/update_memory_md.rs:71
/// `MEMORY.md` mid read-modify-write. This takes an `fs2` exclusive `flock` on a
/// sentinel `.memory-write.lock` file in the workspace; the returned `File`
/// holds the lock until it is dropped (end of the write). `flock` acquisition
/// blocks, so it runs on a blocking thread.
async fn acquire_cross_process_write_lock(workspace_dir: &Path) -> anyhow::Result<std::fs::File> {
let lock_path = workspace_dir.join(".memory-write.lock");
tokio::task::spawn_blocking(move || {
use fs2::FileExt;
if let Some(parent) = lock_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
// Reject a symlinked lock file: a project-controlled `.memory-write.lock`
// symlink (including a dangling one) could otherwise redirect this
// create/open/lock to a path OUTSIDE the already-containment-checked
// workspace, bypassing the symlink hardening applied to MEMORY.md /
// SKILL.md. If it exists it must be a regular file.
if let Ok(meta) = std::fs::symlink_metadata(&lock_path) {
if meta.file_type().is_symlink() {
return Err(anyhow::anyhow!(
"workspace lock file {lock_path:?} is a symlink; refusing to follow it"
));
}
}
let mut opts = std::fs::OpenOptions::new();
opts.create(true).write(true).truncate(false);
#[cfg(unix)]
{
// O_NOFOLLOW closes the TOCTOU window: if a symlink is swapped in
// after the check above, the open fails (ELOOP) rather than follows.
use std::os::unix::fs::OpenOptionsExt;
opts.custom_flags(libc::O_NOFOLLOW);
}
let file = opts
.open(&lock_path)
.map_err(|e| anyhow::anyhow!("open workspace lock file {lock_path:?}: {e}"))?;
file.lock_exclusive()
.map_err(|e| anyhow::anyhow!("acquire workspace write flock: {e}"))?;View on GitHub (pinned to 7491200858)
Solutions
- Delete the symlinked .memory-write.lock from the workspace
- Investigate how the symlink was created — it may indicate tampering
- The next write attempt will recreate a regular lock file
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at src/openhuman/tools/impl/filesystem/update_memory_md.rs:71 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/915e1d937a34e31d.
Report an issue: GitHub.