{"record":{"id":"e988d4008420bb8b","repo":"Hmbown/CodeWhale","slug":"codewhalepet-1","errorCode":"CodewhalePet/1","errorMessage":"A habitat file must be a regular file without links.","messagePattern":"A habitat file must be a regular file without links\\.","errorType":"error_code","errorClass":"NSError","httpStatus":null,"severity":"error","filePath":"pet/swift/PetHabitatStore.swift","lineNumber":121,"sourceCode":"        defer { close(file) }\n        let info = try Self.regular(file)\n        guard info.st_size >= 0 && info.st_size <= limit else { throw Self.invalid(\"The saved habitat exceeds 8 MiB.\") }\n        var value = Data(), buffer = [UInt8](repeating: 0, count: 16_384)\n        while true {\n            let count = Darwin.read(file, &buffer, min(buffer.count, limit + 1 - value.count))\n            if count < 0 && errno == EINTR { continue }\n            guard count >= 0 else { throw Self.ioError() }\n            if count == 0 { break }\n            value.append(contentsOf: buffer.prefix(count))\n            guard value.count <= limit else { throw Self.invalid(\"The saved habitat exceeds its size limit.\") }\n        }\n        return value\n    }\n\n    private static func regular(_ file: Int32) throws -> stat {\n        var info = stat()\n        guard fstat(file, &info) == 0 else { throw ioError() }\n        guard info.st_mode & S_IFMT == S_IFREG, info.st_nlink == 1 else { throw invalid(\"A habitat file must be a regular file without links.\") }\n        return info\n    }\n    private static func ioError() -> Error { NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) }\n    private static func invalid(_ message: String) -> Error { NSError(domain: \"CodewhalePet\", code: 1, userInfo: [NSLocalizedDescriptionKey: message]) }\n}\n","sourceCodeStart":103,"sourceCodeEnd":127,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/swift/PetHabitatStore.swift#L103-L127","documentation":"Thrown by `PetHabitatStore.regular(_:)` in the Swift pet companion when a habitat file's `fstat` shows it is not a regular file (`st_mode & S_IFMT != S_IFREG`) or has a link count other than 1 (`st_nlink != 1`). The store refuses to read or trust habitat files that could be symlinks, hardlinks, fifos, or devices, closing a TOCTOU/hardlink attack window on the local pet state. The error is an NSError with domain `CodewhalePet`, code 1.","triggerScenarios":"Opening a habitat store whose file was replaced by a symlink to another file; a file with multiple hard links (e.g. created via `ln`, backup tools, or git worktrees/checkouts that link files); a special file (FIFO, device) placed at the habitat path.","commonSituations":"Users symlinking pet state into a synced or dotfile-managed directory (Dropbox, dotfiles repo with `ln -s`); backup/restore tools that re-create files as hard links; corrupted or maliciously planted files at the habitat path on macOS.","solutions":["Replace the symlink or hard link with a real regular file: `rm <path> && cp <link-target> <path>` (for symlinks) so `st_nlink == 1`.","Find and isolate extra hard links: `ls -l <path>` (link count) and `find / -samefile <path>`; delete or move the duplicates.","Reset the habitat by deleting the store file and letting Codewhale recreate it.","Exclude the pet habitat directory from dotfile managers/sync tools that create links."],"exampleFix":"// before\n$ ln pet.habitat pet.habitat.backup   # st_nlink == 2\n// after\n$ rm pet.habitat.backup\n$ ls -l pet.habitat                    # link count 1, regular file","handlingStrategy":"validation","validationCode":"import Foundation\nfunc isRegularUnlinkedFile(_ path: String) -> Bool {\n    var st = stat()\n    guard stat(path, &st) == 0 else { return false }\n    return st.st_mode & S_IFMT == S_IFREG && st.st_nlink == 1\n}","typeGuard":"func statIsRegularUnlinked(_ info: stat) -> Bool {\n    info.st_mode & S_IFMT == S_IFREG && info.st_nlink == 1\n}","tryCatchPattern":"do {\n    let store = try PetHabitatStore.open(at: url)\n} catch let e as NSError where e.domain == \"CodewhalePet\" && e.code == 1 {\n    // Habitat path is a symlink/hardlink/special file: recreate as a plain file.\n    try? FileManager.default.removeItem(at: url)\n    FileManager.default.createFile(atPath: url.path, contents: nil)\n}","preventionTips":["Never symlink the pet habitat file into synced or dotfile-managed directories.","Exclude the habitat path from backup tools that recreate files as hard links.","Check link counts with `ls -l` before moving habitat files between machines.","On restore, copy files (cp) rather than linking them."],"tags":["swift","filesystem","security","symlink","validation"],"backgroundTag":"incompatible-source-type","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T01:17:13.364Z"}