Installs into .claude/skills of the current project.
Are you the author of Swift Error Handling Style Workflow?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/gaelic-ghost-swift-error-handling-style-workflow)
---
name: swift-error-handling-style-workflow
description: Design or repair Swift error handling style using throws, typed throws, Result, Optional, AsyncSequence failure types, domain errors, Cocoa bridging, and concise functional recovery paths.
license: Apache-2.0
metadata:
owner: gaelic-ghost
repo: socket
category: swift-language
---
# Swift Error Handling Style Workflow
## Purpose
Make Swift failure behavior clear at the call site and useful when something
breaks.
The house style is concise, typed by default for Swift-owned failure surfaces,
and functional in feel: fallible values should move through explicit carriers,
error messages should explain the failed operation, and recovery should happen
at the boundary that can actually choose a next step.
## Source Check
Use repo-local guidance first. For general language behavior, prefer the Swift
Book, Swift Standard Library docs, Swift Evolution, and Apple Foundation docs:
- [Error Handling in The Swift Programming Language](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling/)
- [SE-0413: Typed throws](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0413-typed-throws.md)
- [Result](https://developer.apple.com/documentation/swift/result)
- [About Imported Cocoa Error Parameters](https://developer.apple.com/documentation/swift/about-imported-cocoa-error-parameters)
- [Handling Cocoa Errors in Swift](https://developer.apple.com/documentation/swift/handling-cocoa-errors-in-swift)
- [LocalizedError](https://developer.apple.com/documentation/foundation/localizederror)
- [CustomNSError](https://developer.apple.com/documentation/foundation/customnserror)
- [RecoverableError](https://developer.apple.com/documentation/foundation/recoverableerror)
## When To Use
- Use this skill when designing or reviewing Swift error surfaces.
- Use this skill when code hides recoverable failures in `nil`, strings, logs, or
broad catch-all wrappers.
- Use this skill when deciding between `throws`, typed throws, `Result`,
`Optional`, `AsyncSequence` failure types, framework errors, or domain errors.
- Use this skill when modernizing nested `do`/`catch`, callback-era
`Result`-passing, weak diagnostics, or awkward Objective-C/Cocoa error
bridging.
## Workflow
1. Identify the failure boundary:
- operation
- inputs
- success value
- expected absence
- recoverable failures
- programmer errors
- framework or transport errors
- async or streaming boundary
2. Choose the carrier:
- nonoptional value when failure is impossible after construction
- `Optional` when absence is expected and not diagnostic
- typed throws for Swift-owned fallible operations when the error type can be
named clearly
- untyped `throws` or `async throws` when the operation forwards broad,
open-ended framework, filesystem, networking, database, plugin, or
dependency failures without adding a useful typed boundary
- `Result` when success or failure must be stored, combined, cached, tested,
or delivered through a non-throwing callback
- `AsyncSequence` failure types when values arrive over time and iteration can
fail
- existing framework errors when the platform already gives a precise error
domain
3. Model domain failures:
- prefer existing framework errors until a concrete custom domain, extension,
or call-site recovery need appears
- prefer small `enum` errors with associated values when the cases are closed
and meaningful
- preserve underlying errors when they help diagnosis
- use `LocalizedError` for user-visible or operator-facing descriptions
- use `CustomNSError` when Cocoa interop, error domains, codes, or user-info
keys matter
- use `RecoverableError` only when the caller can present concrete recovery
choices
4. Keep flow concise:
- use `try` and `try await` for straight-line fallible work
- use `map`, `flatMap`, `mapError`, `Result.get()`, and typed transforms when
the failure value is intentionally part of the pipeline
- prefer functional composition over imperative branching whenever it stays
accurate and readable
- split long chains at diagnostic, side-effect, actor, or async boundaries
- catch narrowly where recovery happens
- let errors propagate when the current layer has no useful recovery decision
5. Improve diagnostics:
- include operation, source, important input identity, likely cause, and next
inspection point when the error reaches a human
- keep low-level details available without dumping secrets or raw payloads
- log at the boundary that has context, not at every propagation hop
- avoid vague messages such as `failed`, `invalid`, or `unknown error`
## House Defaults
- Prefer typed throws for Swift-owned synchronous and structured-concurrency
APIs when the error type can be named clearly.
- Prefer untyped `throws` when forwarding broad framework, filesystem,
networking, database, plugin, or dependency failures without changing their
meaning.
- Prefer `Result` for value-level composition, storage, callback interop, batch
outcomes, and tests that need to assert failure as data.
- Prefer `Optional` only for ordinary absence. Do not erase useful failure
information to make a pipeline look tidy.
- Prefer existing Foundation, Cocoa, SwiftPM, SwiftNIO, Vapor, Hummingbird, or
framework error types until a concrete custom domain, extension, or recovery
need appears.
- Prefer small domain error enums over broad wrapper hierarchies when custom
errors are needed.
- Prefer preserving underlying errors over stringifying them.
- Prefer direct propagation over local catch-and-rethrow wrappers that add no new
context.
- Prefer functional transforms, narrow recovery helpers, and value-level error
composition over broad imperative branching.
- Prefer assertions, preconditions, or non-throwing validation for programmer
mistakes only when recovery is not part of the API contract.
## Typed Throws Guidance
Typed throws is the preferred house style for Swift-owned error surfaces, while
untyped `throws` remains the right tool for open-ended failure domains.
Use typed throws when:
- the operation has a closed domain error set
- the operation is Swift-owned and the error type can be named clearly
- callers benefit from exhaustive `catch` handling
- tests should assert every domain case
- a generic API should preserve its caller's failure type
- embedded, performance-sensitive, or allocation-sensitive code benefits from
carrying a concrete error type
Avoid typed throws when:
- the operation mostly forwards framework, filesystem, networking, database, or
plugin errors without adding a meaningful typed boundary
- the API boundary is public and the error set is likely to grow
- callers would immediately erase the type to `any Error`
- the type annotation makes simple code noisier without changing recovery
## Error Helper Direction
A small shared helper package could become useful if several repositories start
needing the same concise diagnostic, wrapping, or recovery helpers.
Treat that as a separate design decision. A future package might explore generic
helpers, variadic generics or parameter packs, and macros, but do not invent a
local helper framework inside one app or skill unless the repeated call sites
already exist and the package design has been discussed.
Use the root Socket maintainer plan at
`docs/maintainers/errorhandles-package-plan.md` when deciding whether that helper
belongs in Socket or in a separate Swift package repository.
## Example Shapes
Straight-line fallible work:
```swift
func loadManifest(at url: URL) async throws -> Manifest {
let data = try await fetch(url)
return try ManifestDecoder().decode(data)
}
```
Closed domain failures:
```swift
enum ManifestError: Error, Equatable {
case missingName(URL)
case unsupportedVersion(String)
}
func validate(_ manifest: Manifest) throws(ManifestError) -> Manifest {
guard let name = manifest.name else {
throw .missingName(manifest.sourceURL)
}
guard manifest.version.isSupported else {
throw .unsupportedVersion(manifest.version.rawValue)
}
return manifest
}
```
Stored or batched failures:
```swift
let results: [Result<Package, PackageLoadError>] = urls.map { url in
Result { try loadPackage(at: url) }
}
let packages = results.compactMap { try? $0.get() }
let failures = results.compactMap { result -> PackageLoadError? in
guard case let .failure(error) = result else { return nil }
return error
}
```
Operator-facing error context:
```swift
enum PackageLoadError: LocalizedError {
case unreadableManifest(url: URL, underlying: any Error)
var errorDescription: String? {
switch self {
case let .unreadableManifest(url, underlying):
"Could not read Package.swift at \(url.path). Check that the file exists, is readable, and contains valid Swift package syntax. Underlying error: \(underlying)"
}
}
}
```
## Output Shape
Return:
1. `Failure state`: current operation, success value, absence, recoverable
failures, and programmer errors.
2. `Carrier choice`: why `throws`, typed throws, `Result`, `Optional`,
`AsyncSequence`, existing framework errors, or domain errors fit.
3. `House-style changes`: API signatures, error types, propagation, recovery,
and diagnostics to change.
4. `Examples`: compact call-site or implementation sketch.
5. `Validation`: compile, tests, and failure-case checks needed.
## Guardrails
- Do not add error abstraction layers without a real caller, recovery path, or
interop need.
- Do not wrap every underlying error just to make a local enum exhaustive.
- Do not force typed throws onto APIs whose failures are still genuinely
open-ended.
- Do not hide recoverable failures in logs, `nil`, default values, or comments.
- Do not over-functionalize error handling when a narrow `do`/`catch` is clearer.
- Do not catch only to print or log and then continue with corrupted state.