Hmbown/CodeWhale · error · NSError
NSPOSIXErrorDomain
NSPOSIXErrorDomain
Error message
NSPOSIXErrorDomain errno (no message text)
What it means
PetHabitatStore throws an NSError in NSPOSIXErrorDomain whose code is the raw errno when a Darwin system call fails. In this store it originates from ioError(), thrown after failures of fstat, open/openat, write, fsync, linkat/renameat, or flock during load/save. It carries no message text, so the POSIX errno code itself (e.g. ENOENT, EACCES, EINTR-handled elsewhere, ENOSPC, EAGAIN) is the only diagnostic.
Solutions
- Log the NSError code and map it with strerror(code) to identify the actual POSIX errno before changing code.
- If the code is EWOULDBLOCK/EAGAIN, another process holds the flock: retry after the other writer finishes instead of treating it as corruption.
- If the code is ENOSPC, free disk space (or reduce store usage) and retry save.
- If the code is EACCES or EPERM, repair permissions on the store directory (expected 0o700) and lock/data files, and ensure the process user owns them.
- If the code is ELOOP, remove any symlink or hard link that replaced the habitat JSON or lock file and let the store recreate them.
- If the code is EIO or persistent, check disk health / move the store to a local volume (fsync and O_NOFOLLOW semantics can fail on network mounts).
Example fix
// before
do { try store.save(data) } catch { print("save failed") }
// after
do {
try store.save(data)
} catch let error as NSError where error.domain == NSPOSIXErrorDomain {
print("POSIX failure: \(String(cString: strerror(Int32(error.code)))) (errno \(error.code))")
if error.code == Int32(EWOULDBLOCK) { /* another writer holds the lock; retry later */ }
} Defensive patterns
Strategy: try-catch
Validate before calling
import Darwin
func preflightStore(_ url: URL) -> String? {
let fm = FileManager.default
guard fm.fileExists(atPath: url.path) else { return nil }
guard (try? fm.attributesOfItem(atPath: url.path))[.type] as? FileAttributeType == .typeRegular else {
return "Store path is not a regular file/symlink present"
}
guard fm.isWritableFile(atPath: url.path) else { return "No write permission on store directory" }
return nil
} Type guard
func isPosixStoreError(_ error: Error) -> Bool {
(error as NSError).domain == NSPOSIXErrorDomain
}
func errno(of error: Error) -> Int32 {
Int32((error as NSError).code)
} Try / catch
do {
try store.save(data)
} catch let e as NSError where e.domain == NSPOSIXErrorDomain {
switch Int32(e.code) {
case EWOULDBLOCK, EAGAIN: scheduleRetry()
case ENOSPC: freeDiskSpaceThenRetry()
case EACCES, EPERM: promptUserToFixPermissions()
default: reportFatal(String(cString: strerror(Int32(e.code))))
}
} catch {
reportFatal(error.localizedDescription)
} Prevention
- Catch NSPOSIXErrorDomain specifically and decode errno with strerror() instead of relying on the empty message text.
- Handle EWOULDBLOCK from the non-blocking flock with backoff/retry, since concurrent writers are an expected condition.
- Keep the store on a local, writable volume and verify the directory keeps its 0o700 permissions.
- Never symlink or hard-link the habitat JSON, segment archives, or lock files — O_NOFOLLOW and nlink==1 checks will fail the store.
- Monitor free disk space before saves; write failures surface as raw errno (ENOSPC/EIO) with no message.
When it happens
Trigger: fstat on a lock or data file fails in regular(); open/openat with O_NOFOLLOW fails because the target is a symlink (ELOOP) or missing and errno != ENOENT; write returns <= 0 other than EINTR (e.g. ENOSPC, EIO); fsync fails; flock(LOCK_EX|LOCK_NB) returns EWOULDBLOCK because another process holds the lock; renameat/linkat fail; or the lock file cannot be reopened inside coordinated().
Common situations: Disk full when saving habitat data; two app processes contending for the non-blocking exclusive lock; the store directory or lock file replaced by a symlink or a hard-linked file; permission changes on the 0o700 directory; file system errors on external/network volumes where fsync or rename fails.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/f4816a7c098d545c.
Report an issue: GitHub.
Appendix: source
Thrown at pet/swift/PetHabitatStore.swift:120
guard file >= 0 else { throw Self.ioError() }
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)