package clawdforge import ( "errors" "fmt" ) // ErrAuth is the sentinel returned for 401/403 responses from clawdforge. // Use errors.Is(err, ErrAuth) to detect auth failures. var ErrAuth = errors.New("clawdforge: authentication failed") // APIError carries a non-2xx response from clawdforge — status code plus // the response body (truncated to a sane size). It is returned for any // HTTP status >= 400 that isn't an auth failure. type APIError struct { StatusCode int // Body is the raw response body (truncated). Useful for debugging. Body string // Message is a short human-readable summary derived from the body // when the body is JSON of the form {"error": "..."} or {"detail": "..."}. Message string } func (e *APIError) Error() string { if e.Message != "" { return fmt.Sprintf("clawdforge: HTTP %d: %s", e.StatusCode, e.Message) } return fmt.Sprintf("clawdforge: HTTP %d: %s", e.StatusCode, e.Body) } // TransportError wraps a low-level network/transport failure (DNS, connect // refused, TLS, EOF, context cancellation, etc.). The wrapped error is // available via errors.Unwrap. type TransportError struct { Op string Err error } func (e *TransportError) Error() string { return fmt.Sprintf("clawdforge: transport %s: %v", e.Op, e.Err) } func (e *TransportError) Unwrap() error { return e.Err } // RunFailure represents a 502 response from /run — clawdforge accepted the // request but `claude -p` failed (timeout, non-zero exit, etc.). The body // fields mirror the server's failure shape. // // RunFailure satisfies APIError-like semantics by also embedding the status // code via the underlying APIError, but it's a distinct type so callers can // branch on errors.As(err, &cf.RunFailure{}). type RunFailure struct { StatusCode int Err string `json:"error"` Stderr string `json:"stderr"` DurationMS int `json:"duration_ms"` StopReason string `json:"stop_reason"` } func (e *RunFailure) Error() string { return fmt.Sprintf("clawdforge: run failed (stop_reason=%s): %s", e.StopReason, e.Err) }