Skip to content

Error Types

envpkt uses discriminated union types for errors. Each error has a _tag field for pattern matching.

Errors from loading and parsing envpkt.toml.

type ConfigError =
| { readonly _tag: "FileNotFound"; readonly path: string }
| { readonly _tag: "ParseError"; readonly message: string }
| { readonly _tag: "ValidationError"; readonly errors: List<string> }
| { readonly _tag: "ReadError"; readonly message: string }

| Tag | When | | ----------------- | ---------------------------------------------- | | FileNotFound | The specified config file does not exist | | ParseError | TOML syntax error | | ValidationError | Schema validation failed (with list of errors) | | ReadError | File system read error |

Errors from catalog resolution.

type CatalogError =
| { readonly _tag: "CatalogNotFound"; readonly path: string }
| { readonly _tag: "CatalogLoadError"; readonly message: string }
| { readonly _tag: "SecretNotInCatalog"; readonly key: string; readonly catalogPath: string }
| { readonly _tag: "MissingSecretsList"; readonly message: string }

| Tag | When | | -------------------- | --------------------------------------------- | | CatalogNotFound | The catalog file path does not exist | | CatalogLoadError | Catalog file failed to load or parse | | SecretNotInCatalog | Agent references a key not defined in catalog | | MissingSecretsList | Agent uses catalog but has no secrets array |

Errors from fnox integration.

type FnoxError =
| { readonly _tag: "FnoxNotFound"; readonly message: string }
| { readonly _tag: "FnoxCliError"; readonly message: string }
| { readonly _tag: "FnoxParseError"; readonly message: string }

| Tag | When | | ---------------- | ------------------------------- | | FnoxNotFound | fnox CLI or config not found | | FnoxCliError | fnox command execution failed | | FnoxParseError | fnox output could not be parsed |

Errors from agent identity operations.

type IdentityError =
| { readonly _tag: "IdentityNotFound"; readonly path: string }
| { readonly _tag: "IdentityUnreadable"; readonly path: string; readonly message: string }
| (IdentityFileError & { readonly source: string })
type IdentityFileError =
| { readonly _tag: "NoIdentityKey" }
| { readonly _tag: "PassphraseProtectedIdentity" }
| { readonly _tag: "UnsupportedIdentity"; readonly kind: "plugin" | "ssh" | "unknown"; readonly line: number }

source is the key file’s path, or ENVPKT_AGE_KEY for the inline key. None of these errors carries key material; describeIdentityError(err) turns any of them into a message.

| Tag | When | | ----------------------------- | ------------------------------------------------------------------------ | | IdentityNotFound | Key file not found at the given path | | IdentityUnreadable | Key file exists but can’t be read (permissions, a directory, I/O error) | | NoIdentityKey | The key source has no AGE-SECRET-KEY-… line | | PassphraseProtectedIdentity | The key file is passphrase-protected; unwrap it once with age -d | | UnsupportedIdentity | A line is an age plugin key, an SSH key, or not a key; line says which |

In boot(), any of these stops boot when sealed values are declared, even under warnOnly.

Errors from key generation and config update operations.

type KeygenError =
| { readonly _tag: "KeygenFailed"; readonly message: string }
| { readonly _tag: "KeyExists"; readonly path: string }
| { readonly _tag: "WriteError"; readonly message: string }
| { readonly _tag: "ConfigUpdateError"; readonly message: string }

| Tag | When | | ------------------- | ------------------------------------------------------- | | KeygenFailed | Key generation failed | | KeyExists | Identity file already exists (no overwrite; pass -o) | | WriteError | Failed to write identity file or create directory | | ConfigUpdateError | Failed to read/write envpkt.toml for recipient update |

Errors from validating from_key alias references in envpkt.toml. Raised at config load time before any secret resolution begins.

type AliasError =
| {
readonly _tag: "AliasInvalidSyntax"
readonly key: string
readonly kind: "secret" | "env"
readonly value: string
}
| { readonly _tag: "AliasTargetMissing"; readonly key: string; readonly target: string }
| { readonly _tag: "AliasSelfReference"; readonly key: string }
| { readonly _tag: "AliasChained"; readonly key: string; readonly target: string }
| {
readonly _tag: "AliasCrossType"
readonly key: string
readonly kind: "secret" | "env"
readonly targetKind: "secret" | "env"
}
| {
readonly _tag: "AliasValueConflict"
readonly key: string
readonly kind: "secret" | "env"
readonly field: string
}

| Tag | When | | -------------------- | ----------------------------------------------------------------------------------- | | AliasInvalidSyntax | from_key is not formatted as "secret.<KEY>" or "env.<KEY>" | | AliasTargetMissing | from_key references a target that doesn’t exist in the resolved config | | AliasSelfReference | An entry’s from_key points at itself | | AliasChained | The target is itself an alias (only single-hop aliasing is supported) | | AliasCrossType | A secret entry aliases an env entry or vice versa (same-type aliasing only) | | AliasValueConflict | The entry declares both from_key and a value field (encrypted_value or value) |

Returned by buildMcpHeaders and printed by envpkt headers. McpConfigError is the subset that validateMcpHeaders returns.

type McpConfigError =
| { readonly _tag: "McpHeaderInvalid"; readonly key: string; readonly server: string; readonly header: string }
| { readonly _tag: "McpSchemeInvalid"; readonly key: string; readonly server: string; readonly scheme: string }
| {
readonly _tag: "McpHeaderDuplicate"
readonly server: string
readonly header: string
readonly keys: ReadonlyArray<string>
}
type McpHeadersError =
| McpConfigError
| { readonly _tag: "McpServerUnknown"; readonly server: string }
| { readonly _tag: "McpValueUnresolved"; readonly server: string; readonly keys: ReadonlyArray<string> }
| { readonly _tag: "McpValueInvalid"; readonly server: string; readonly keys: ReadonlyArray<string> }

| Tag | When | | -------------------- | ----------------------------------------------------------------------------- | | McpHeaderInvalid | A header is not a valid HTTP header name (empty, spaces, : and so on) | | McpSchemeInvalid | A non-empty scheme is not a single HTTP token (e.g. "Bearer token") | | McpHeaderDuplicate | Two bindings claim the same (server, header) pair (header case-insensitive) | | McpServerUnknown | No secret’s mcp list names the requested server | | McpValueUnresolved | A bound secret has no value, or an empty one, after resolution | | McpValueInvalid | A bound value contains a line break or NUL, so it can’t be sent as a header |

Union of all error types that can occur during boot:

type BootError =
| ConfigError
| FnoxError
| CatalogError
| AliasError
| { readonly _tag: "AuditFailed"; readonly audit: AuditResult; readonly message: string }
| {
readonly _tag: "SealKeyUnavailable"
readonly sealedKeys: ReadonlyArray<string>
readonly searched: ReadonlyArray<string>
readonly configuredKeyFile?: string
}
| IdentityError

The AuditFailed variant includes the full AuditResult for inspection. SealKeyUnavailable means sealed values are declared but no age key was found anywhere; searched lists each place envpkt looked, in order. Boot refuses rather than inject empty values, even under warnOnly.

Use _tag for exhaustive pattern matching:

import type { ConfigError } from "envpkt"
function handleError(err: ConfigError): string {
switch (err._tag) {
case "FileNotFound":
return `Config not found: ${err.path}`
case "ParseError":
return `TOML syntax error: ${err.message}`
case "ValidationError":
return `Validation failed: ${err.errors.toArray().join(", ")}`
case "ReadError":
return `Read error: ${err.message}`
}
}

Or use functype’s Either.fold:

loadConfig("envpkt.toml").fold(
(err) => console.error(`[${err._tag}]`, err),
(config) => console.log("Loaded:", config),
)