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

  1. Log the NSError code and map it with strerror(code) to identify the actual POSIX errno before changing code.
  2. If the code is EWOULDBLOCK/EAGAIN, another process holds the flock: retry after the other writer finishes instead of treating it as corruption.
  3. If the code is ENOSPC, free disk space (or reduce store usage) and retry save.
  4. 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.
  5. 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.
  6. 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

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)