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
- 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.
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
- 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.
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
- Automation lock must not be a reparse point
- could not securely open
- external credential path escapes its absolute root
- external credential path must name a non-reparse regular…
- file is not a regular single-link file
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)