Hmbown/CodeWhale · error · NSError

CodewhalePet/1

CodewhalePet/1

Error message

A habitat file must be a regular file without links.

What it means

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.

Solutions

  1. Replace the symlink or hard link with a real regular file: `rm <path> && cp <link-target> <path>` (for symlinks) so `st_nlink == 1`.
  2. Find and isolate extra hard links: `ls -l <path>` (link count) and `find / -samefile <path>`; delete or move the duplicates.
  3. Reset the habitat by deleting the store file and letting Codewhale recreate it.
  4. Exclude the pet habitat directory from dotfile managers/sync tools that create links.

Example fix

// before
$ ln pet.habitat pet.habitat.backup   # st_nlink == 2
// after
$ rm pet.habitat.backup
$ ls -l pet.habitat                    # link count 1, regular file
Defensive patterns

Strategy: validation

Validate before calling

import Foundation
func isRegularUnlinkedFile(_ path: String) -> Bool {
    var st = stat()
    guard stat(path, &st) == 0 else { return false }
    return st.st_mode & S_IFMT == S_IFREG && st.st_nlink == 1
}

Type guard

func statIsRegularUnlinked(_ info: stat) -> Bool {
    info.st_mode & S_IFMT == S_IFREG && info.st_nlink == 1
}

Try / catch

do {
    let store = try PetHabitatStore.open(at: url)
} catch let e as NSError where e.domain == "CodewhalePet" && e.code == 1 {
    // Habitat path is a symlink/hardlink/special file: recreate as a plain file.
    try? FileManager.default.removeItem(at: url)
    FileManager.default.createFile(atPath: url.path, contents: nil)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/e988d4008420bb8b. Report an issue: GitHub.

Appendix: source

Thrown at pet/swift/PetHabitatStore.swift:121

        defer { close(file) }
        let info = try Self.regular(file)
        guard info.st_size >= 0 && info.st_size <= limit else { throw Self.invalid("The saved habitat exceeds 8 MiB.") }
        var value = Data(), buffer = [UInt8](repeating: 0, count: 16_384)
        while true {
            let count = Darwin.read(file, &buffer, min(buffer.count, limit + 1 - value.count))
            if count < 0 && errno == EINTR { continue }
            guard count >= 0 else { throw Self.ioError() }
            if count == 0 { break }
            value.append(contentsOf: buffer.prefix(count))
            guard value.count <= limit else { throw Self.invalid("The saved habitat exceeds its size limit.") }
        }
        return value
    }

    private static func regular(_ file: Int32) throws -> stat {
        var info = stat()
        guard fstat(file, &info) == 0 else { throw ioError() }
        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.") }
        return info
    }
    private static func ioError() -> Error { NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) }
    private static func invalid(_ message: String) -> Error { NSError(domain: "CodewhalePet", code: 1, userInfo: [NSLocalizedDescriptionKey: message]) }
}

View on GitHub (pinned to 433685b202)