errs

package
v1.1.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package errs defines sentinel errors for the nself CLI.

Named "errs" (not "errors") to avoid shadowing the Go standard library. Other packages import as: nself/internal/errs Usage: errs.ErrDockerNotRunning

Package errs (exit codes) defines the canonical exit-code contract for the nSelf CLI. Every command path that can fail must map its failure to one of these codes so wrappers (CI runners, schedulers, ops scripts) can branch on outcome class without parsing stderr.

The codes are intentionally small and stable. Adding a new class requires a release-plan note. Re-using an existing class is preferred over inventing a new one.

Index

Constants

View Source
const (
	// ExitOK indicates successful completion.
	ExitOK = 0

	// ExitUserError indicates the caller supplied invalid input: bad flag
	// value, missing required argument, malformed env, unknown subcommand.
	// The fix is on the user side.
	ExitUserError = 1

	// ExitInfraError indicates a downstream infrastructure failure: docker
	// daemon down, port collision, network unreachable, disk full, plugin
	// runtime crash. Retrying after fixing the host condition may succeed.
	ExitInfraError = 2

	// ExitAuthError indicates an authentication or authorization failure:
	// invalid license key, expired key, tier insufficient for the requested
	// plugin, missing OAuth credentials.
	ExitAuthError = 3

	// ExitDestructiveBlocked indicates a destructive action was refused by
	// the safety guards: missing --force on prod, force_local mismatch,
	// confirmation prompt declined.
	ExitDestructiveBlocked = 4
)

Canonical exit codes. Must match the contract documented in .claude/docs/standards/exit-codes.md and surfaced via 'nself --help' in the EXIT CODES section.

Variables

View Source
var (
	// Docker
	ErrDockerNotRunning   = errors.New("docker daemon is not running")
	ErrDockerNotInstalled = errors.New("docker not found in PATH")
	ErrComposeNotFound    = errors.New("docker-compose.yml not found — run 'nself build' first")
	ErrPortConflict       = errors.New("port already in use")

	// Config
	ErrWeakPassword       = errors.New("password does not meet minimum length")
	ErrInsecurePassword   = errors.New("password matches insecure pattern")
	ErrInvalidProjectName = errors.New("invalid project name format")
	ErrInvalidCORS        = errors.New("CORS wildcards not allowed in production")

	// Health
	ErrServiceUnhealthy = errors.New("service health check failed")
	ErrHealthTimeout    = errors.New("health check timed out")
	ErrServiceNotFound  = errors.New("service not found")

	// Plugin
	ErrInvalidLicenseKey         = errors.New("invalid license key format")
	ErrLicenseTierTooLow         = errors.New("license tier does not include this plugin")
	ErrLicenseExpired            = errors.New("license key is expired")
	ErrLicenseNetworkUnavailable = errors.New("cannot validate license: network unavailable and no valid cache")
	ErrPluginNotFound            = errors.New("plugin not found in registry")
	ErrPluginManifest            = errors.New("invalid plugin manifest")
	ErrCircularDependency        = errors.New("circular plugin dependency detected")
	ErrPluginUnsigned            = errors.New("stable plugin is missing required signature — install refused")
	ErrPluginMissingChecksum     = errors.New("stable plugin is missing required checksum — install refused")

	// SSL
	ErrMkcertNotFound      = errors.New("mkcert not installed — falling back to OpenSSL")
	ErrSSLGenerationFailed = errors.New("SSL certificate generation failed")

	// Nginx
	ErrDuplicateRoute = errors.New("duplicate nginx route detected")

	// Domain / Port
	ErrInvalidDomain = errors.New("invalid domain name")
	ErrInvalidPort   = errors.New("invalid port number")

	// Database
	ErrDatabaseNotRunning  = errors.New("database is not running")
	ErrMigrationFailed     = errors.New("database migration failed")
	ErrBackupFailed        = errors.New("database backup failed")
	ErrBackupNotFound      = errors.New("backup not found")
	ErrBackupVerifyFailed  = errors.New("backup verification failed")
	ErrBackupRestoreFailed = errors.New("backup restore failed")
	ErrBackupEncryptFailed = errors.New("backup encryption failed")
	ErrBackupDecryptFailed = errors.New("backup decryption failed")
	ErrBackupRemoteFailed  = errors.New("remote backup operation failed")
	ErrBackupPruneFailed   = errors.New("backup pruning failed")
	ErrWALArchiveFailed    = errors.New("WAL archive failed")

	// Disaster Recovery
	ErrDRDrillFailed    = errors.New("DR drill failed")
	ErrDRPromoteFailed  = errors.New("standby promotion failed")
	ErrDRRollbackFailed = errors.New("DR rollback failed")
	ErrDRFenceFailed    = errors.New("split-brain fence failed")
)
View Source
var ErrDestructiveBlocked = sentinel("destructive action blocked by safety gate")

ErrDestructiveBlocked is the package-level sentinel that command glue can wrap with fmt.Errorf("...: %w", errs.ErrDestructiveBlocked) so that ExitCodeFor classifies the failure as exit code 4 without an import cycle through internal/confirm.

View Source
var Registry = map[string]CodeEntry{

	"E001": {
		Category:   "docker",
		Summary:    "Docker not installed",
		DefaultWhy: "The docker binary was not found in PATH.",
		DefaultFix: "Install Docker: https://docs.docker.com/get-docker/",
		DocsPath:   "reference/error-codes#e001",
	},
	"E002": {
		Category:   "docker",
		Summary:    "Docker daemon not running",
		DefaultWhy: "The Docker daemon is not responding to commands.",
		DefaultFix: "Start Docker Desktop or run: sudo systemctl start docker",
		DocsPath:   "reference/error-codes#e002",
	},
	"E003": {
		Category:   "docker",
		Summary:    "Docker Compose not available",
		DefaultWhy: "docker compose v2 plugin is not installed.",
		DefaultFix: "Update Docker Desktop or install the compose plugin manually.",
		DocsPath:   "reference/error-codes#e003",
	},
	"E004": {
		Category:   "docker",
		Summary:    "docker-compose.yml not found",
		DefaultWhy: "No docker-compose.yml exists in the project directory.",
		DefaultFix: "Run 'nself build' to generate the compose file.",
		DocsPath:   "reference/error-codes#e004",
	},
	"E005": {
		Category:   "docker",
		Summary:    "Port conflict",
		DefaultWhy: "A required port is already in use by another process.",
		DefaultFix: "Run 'nself doctor' to identify the conflict, then stop the conflicting process or change the port in .env.",
		DocsPath:   "reference/error-codes#e005",
	},

	"E050": {
		Category:   "config",
		Summary:    "No .env file found",
		DefaultWhy: "No .env or .env.dev file exists in the project directory.",
		DefaultFix: "Run 'nself init' to generate a configuration file.",
		DocsPath:   "reference/error-codes#e050",
	},
	"E051": {
		Category:   "config",
		Summary:    "Invalid config key",
		DefaultWhy: "The configuration key contains invalid characters.",
		DefaultFix: "Config keys must contain only A-Z, 0-9, and underscores.",
		DocsPath:   "reference/error-codes#e051",
	},
	"E052": {
		Category:   "config",
		Summary:    "Config validation failed",
		DefaultWhy: "One or more configuration values are invalid.",
		DefaultFix: "Run 'nself config validate' to see all issues, then fix the reported values.",
		DocsPath:   "reference/error-codes#e052",
	},
	"E053": {
		Category:   "config",
		Summary:    "Weak password detected",
		DefaultWhy: "A password field does not meet minimum length or contains insecure patterns.",
		DefaultFix: "Use a strong, randomly generated password of at least 16 characters.",
		DocsPath:   "reference/error-codes#e053",
	},
	"E054": {
		Category:   "config",
		Summary:    "Invalid project name",
		DefaultWhy: "Project name contains characters not allowed in Docker and DNS contexts.",
		DefaultFix: "Use only lowercase letters, numbers, and hyphens. Must start with a letter.",
		DocsPath:   "reference/error-codes#e054",
	},
	"E055": {
		Category:   "config",
		Summary:    "Duplicate route detected",
		DefaultWhy: "Two or more services are configured with the same nginx route.",
		DefaultFix: "Check ROUTE values in .env and ensure each service has a unique route.",
		DocsPath:   "reference/error-codes#e055",
	},
	"E056": {
		Category:   "config",
		Summary:    "Unknown config key",
		DefaultWhy: "The key is not recognized by nself.",
		DefaultFix: "Check for typos. Run 'nself config list' to see all valid keys.",
		DocsPath:   "reference/error-codes#e056",
	},

	"E100": {
		Category:   "plugin",
		Summary:    "Plugin not found",
		DefaultWhy: "The requested plugin does not exist in the registry.",
		DefaultFix: "Run 'nself plugin list' to see available plugins.",
		DocsPath:   "reference/error-codes#e100",
	},
	"E101": {
		Category:   "plugin",
		Summary:    "Invalid license key",
		DefaultWhy: "The license key format is invalid.",
		DefaultFix: "License keys start with 'nself_pro_' followed by 32+ characters. Check your key.",
		DocsPath:   "reference/error-codes#e101",
	},
	"E102": {
		Category:   "plugin",
		Summary:    "License tier insufficient",
		DefaultWhy: "Your license tier does not include this plugin.",
		DefaultFix: "Upgrade your plan at https://nself.org/pricing",
		DocsPath:   "reference/error-codes#e102",
	},
	"E103": {
		Category:   "plugin",
		Summary:    "License expired",
		DefaultWhy: "The license key has expired.",
		DefaultFix: "Renew your license at https://nself.org/account",
		DocsPath:   "reference/error-codes#e103",
	},
	"E104": {
		Category:   "plugin",
		Summary:    "License validation failed (network)",
		DefaultWhy: "Cannot reach the license server and no valid local cache exists.",
		DefaultFix: "Check your internet connection. Previously validated licenses work offline for 7 days.",
		DocsPath:   "reference/error-codes#e104",
	},
	"E105": {
		Category:   "plugin",
		Summary:    "Circular plugin dependency",
		DefaultWhy: "Plugin dependency graph contains a cycle.",
		DefaultFix: "Report this as a bug at https://github.com/nself-org/cli/issues",
		DocsPath:   "reference/error-codes#e105",
	},

	"E150": {
		Category:   "ssl",
		Summary:    "mkcert not installed",
		DefaultWhy: "mkcert is not installed; falling back to OpenSSL self-signed certs.",
		DefaultFix: "Install mkcert: brew install mkcert (macOS) or see https://github.com/FiloSottile/mkcert",
		DocsPath:   "reference/error-codes#e150",
	},
	"E151": {
		Category:   "ssl",
		Summary:    "SSL certificate generation failed",
		DefaultWhy: "Could not generate SSL certificates for the configured domain.",
		DefaultFix: "Check domain configuration and ensure openssl is available.",
		DocsPath:   "reference/error-codes#e151",
	},

	"E200": {
		Category:   "database",
		Summary:    "Database not running",
		DefaultWhy: "PostgreSQL container is not running or not accepting connections.",
		DefaultFix: "Run 'nself start' to start all services, or 'nself doctor' to diagnose.",
		DocsPath:   "reference/error-codes#e200",
	},
	"E201": {
		Category:   "database",
		Summary:    "Migration failed",
		DefaultWhy: "A database migration could not be applied.",
		DefaultFix: "Check the migration SQL for errors. Run 'nself db migrate --status' to see pending migrations.",
		DocsPath:   "reference/error-codes#e201",
	},
	"E202": {
		Category:   "database",
		Summary:    "Backup failed",
		DefaultWhy: "Database backup operation failed.",
		DefaultFix: "Ensure sufficient disk space and that the database is running.",
		DocsPath:   "reference/error-codes#e202",
	},

	"E250": {
		Category:   "health",
		Summary:    "Service unhealthy",
		DefaultWhy: "A service health check returned an unhealthy status.",
		DefaultFix: "Run 'nself doctor --verbose' for detailed diagnostics.",
		DocsPath:   "reference/error-codes#e250",
	},
	"E251": {
		Category:   "health",
		Summary:    "Health check timeout",
		DefaultWhy: "The health check did not complete within the timeout period.",
		DefaultFix: "The service may be starting slowly. Wait and retry, or check logs with 'nself logs'.",
		DocsPath:   "reference/error-codes#e251",
	},

	"E300": {
		Category:   "init",
		Summary:    "Project already initialized",
		DefaultWhy: "A .env file already exists in this directory.",
		DefaultFix: "Use 'nself config set' to modify existing config, or delete .env to reinitialize.",
		DocsPath:   "reference/error-codes#e300",
	},
	"E301": {
		Category:   "init",
		Summary:    "Source directory detected",
		DefaultWhy: "You are running nself inside the CLI source repository.",
		DefaultFix: "Change to your project directory first: cd /path/to/your/project",
		DocsPath:   "reference/error-codes#e301",
	},

	"E350": {
		Category:   "domain",
		Summary:    "Invalid domain name",
		DefaultWhy: "The configured domain name is not valid.",
		DefaultFix: "Use a valid domain like 'example.com' or 'localhost'.",
		DocsPath:   "reference/error-codes#e350",
	},
	"E351": {
		Category:   "domain",
		Summary:    "Invalid port number",
		DefaultWhy: "Port number is outside the valid range (1-65535).",
		DefaultFix: "Use a port number between 1024 and 65535 for non-privileged ports.",
		DocsPath:   "reference/error-codes#e351",
	},
}

Registry maps error codes (E001..E999) to their metadata. New codes must be added here before use. Keep codes sequential within each category.

Functions

func Categories added in v1.0.6

func Categories() []string

Categories returns all unique category names from the registry, sorted.

func ExitCodeFor added in v1.0.16

func ExitCodeFor(err error) int

ExitCodeFor classifies a top-level error into one of the canonical exit codes. Used by main() to map RunE returns into process exit status.

Classification order:

  1. Auth-class sentinels in this package (license errors).
  2. Destructive-blocked sentinels (force_local guard, confirmation cancel). Callers in internal/confirm import errs implicitly via errors.Is.
  3. Infra-class sentinels (docker, db, ports).
  4. Everything else defaults to user-error (1).

Returning ExitOK is reserved for nil errors and never inferred from classification — the caller must check err == nil first.

Types

type CLIError added in v1.0.6

type CLIError struct {
	// Code is the error code (e.g., "E001"). Used for documentation cross-reference.
	Code string

	// What describes what happened in plain language.
	What string

	// Why explains the root cause.
	Why string

	// Fix provides exact steps the user can take to resolve the issue.
	Fix string

	// DocsPath is the relative docs URL path (e.g., "reference/error-codes#e001").
	DocsPath string

	// Wrapped is an optional underlying error for programmatic inspection.
	Wrapped error
}

CLIError is the structured error type for all user-facing CLI errors. Every error shown to users must use this type to ensure consistent formatting with actionable guidance.

func New added in v1.0.6

func New(code, what string) *CLIError

New creates a new CLIError from a registered error code. If the code is not registered, it falls back to a generic error with the given message.

func Newf added in v1.0.6

func Newf(code, format string, args ...interface{}) *CLIError

Newf creates a CLIError with a formatted What message.

func Wrap added in v1.0.6

func Wrap(code string, what string, err error) *CLIError

Wrap creates a CLIError that wraps an underlying error.

func (*CLIError) Error added in v1.0.6

func (e *CLIError) Error() string

Error implements the error interface with the structured 4-field format.

func (*CLIError) Unwrap added in v1.0.6

func (e *CLIError) Unwrap() error

Unwrap returns the underlying error for errors.Is/As support.

func (*CLIError) WithFix added in v1.0.6

func (e *CLIError) WithFix(fix string) *CLIError

WithFix returns a copy of the error with a custom Fix field.

func (*CLIError) WithWhy added in v1.0.6

func (e *CLIError) WithWhy(why string) *CLIError

WithWhy returns a copy of the error with a custom Why field.

type CodeEntry added in v1.0.6

type CodeEntry struct {
	// Category groups related errors (e.g., "docker", "config", "plugin").
	Category string

	// Summary is a short description of this error class.
	Summary string

	// DefaultWhy is the default root-cause explanation.
	DefaultWhy string

	// DefaultFix is the default remediation steps.
	DefaultFix string

	// DocsPath is the docs URL path fragment.
	DocsPath string
}

CodeEntry defines a registered error code with default guidance fields.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL