{"record":{"id":"b32fe90608a2164e","repo":"Hmbown/CodeWhale","slug":"has-multiple-filesystem-links-not-a-unique-wor","errorCode":null,"errorMessage":"{} has multiple filesystem links, not a unique workspace-owned file","messagePattern":"(.+?) has multiple filesystem links, not a unique workspace-owned file","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/lib.rs","lineNumber":2625,"sourceCode":"            continue;\n        }\n        if ch == '$' {\n            return true;\n        }\n    }\n    false\n}\n\nfn read_stable_workspace_dotenv(path: &Path) -> Result<Vec<u8>> {\n    let mut file = open_workspace_dotenv_without_following_links(path)?;\n    let metadata = file\n        .metadata()\n        .map_err(|error| anyhow!(\"could not inspect {}: {error}\", path.display()))?;\n    if !metadata.is_file() {\n        bail!(\"{} is not a regular file\", path.display());\n    }\n    if workspace_dotenv_has_multiple_links(&file, &metadata)? {\n        bail!(\n            \"{} has multiple filesystem links, not a unique workspace-owned file\",\n            path.display()\n        );\n    }\n    if metadata.len() > MAX_WORKSPACE_DOTENV_BYTES {\n        bail!(\n            \"{} exceeds the {} byte workspace .env limit\",\n            path.display(),\n            MAX_WORKSPACE_DOTENV_BYTES\n        );\n    }\n\n    let mut contents = Vec::with_capacity(metadata.len() as usize);\n    (&mut file)\n        .take(MAX_WORKSPACE_DOTENV_BYTES + 1)\n        .read_to_end(&mut contents)\n        .map_err(|error| anyhow!(\"could not read {}: {error}\", path.display()))?;\n    if contents.len() as u64 > MAX_WORKSPACE_DOTENV_BYTES {","sourceCodeStart":2607,"sourceCodeEnd":2643,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/lib.rs#L2607-L2643","documentation":"After the regular-file check, workspace_dotenv_has_multiple_links verifies the opened file's link count is exactly 1. A hard link (st_nlink > 1) means another path elsewhere on the same filesystem can mutate the credentials the workspace reads, so the load is refused to keep the credential file uniquely workspace-owned — the hardlink sibling of the symlink rule.","triggerScenarios":"Backup/sync tooling that hard-links .env (ln, rsync --link-dest, snapshot/cache managers); deliberately sharing one credential file between multiple checkouts via hard links; a hard link created between open and the link-count check.","commonSituations":"Build caches and dedup tools hardlinking dotfiles; monorepo scripts sharing one .env across worktrees; disk-space-saving tricks that backfire on credential files.","solutions":["Break the link: copy the content to a fresh file (`cp .env /tmp/x && rm .env && cp /tmp/x .env` or rewrite in place)","Stop hardlink-based cache/dedup tooling from managing workspace .env files","Give each checkout its own literal .env instead of sharing via links"],"exampleFix":"# before\nstat -c '%h %n' .env   # 3 .env   (three hard links)\n\n# after\ncp .env .env.tmp && rm .env && mv .env.tmp .env\nstat -c '%h %n' .env   # 1 .env","handlingStrategy":"validation","validationCode":"# Link count must be 1\n[ \"$(stat -c '%h' .env 2>/dev/null || stat -f '%l' .env)\" = '1' ] || {\n  echo '.env has multiple hard links'; exit 2\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Exclude .env from hardlink-deduping backup/cache tools","Give each checkout its own literal .env","After running dedup/sync tooling, verify link count is 1"],"tags":["dotenv","security","filesystem","hardlink"],"backgroundTag":"hardlink-security-check","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}