Runbook

Start from the observable symptom. CLI errors are JSON on stderr with exit 1; HTTP errors are {"error":{code,message,retryable}}. Do not put message bodies, tokens, passwords, or Skarbiec payloads in an incident report.

Overview#

Start from the observable symptom. CLI errors are JSON on stderr with exit 1; HTTP errors are {"error":{code,message,retryable}}. Do not put message bodies, tokens, passwords, or Skarbiec payloads in an incident report.

Service will not start#

Code / exact messageMeaningAction
NON_LOOPBACK_BIND_REFUSED — Skrzynka serves only loopback addresses--bind IP is not loopbackUse 127.0.0.1:<port> or [::1]:<port>; do not proxy this as a remote shared API
POLL_INTERVAL_INVALID — poll interval must be between 15 and 86400 secondsinvalid process intervalChoose 15–86400
DATABASE_SCHEMA_UNSUPPORTED — database schema {n} is not supported by this build (expected at most 3)database is newer than this binaryStop all processes; use a compatible build or restore the stopped-service pre-upgrade backup
INTERNAL_ERROR — local state directory could not be createdparent path cannot be createdFix path ownership/permissions or choose --database elsewhere
INTERNAL_ERROR — loopback API address could not be boundport/address is occupied or unavailableStop the conflicting process or choose another loopback port
INTERNAL_ERROR — central identity URL is invalid / central identity configuration is incompleteenvironment is malformed or not HTTPSCorrect SUPABASE_URL and nonempty SUPABASE_ANON_KEY

Do not run two Skrzynka processes against the same database. Back up the stopped database plus -wal and -shm companions.

/v1 returns no data#

HTTP/codeExact messageAction
401 AUTHENTICATION_REQUIREDa valid Wisent session is requiredSupply Authorization: Bearer ... and a valid X-Wisent-Organization-ID; refresh an expired session
403 ORGANIZATION_ACCESS_DENIEDthe signed-in account does not belong to the selected organizationSelect an organization in the verified user's membership
502 IDENTITY_UNAVAILABLE (retryable:true)central identity verification is unavailableRestore identity connectivity/configuration, then retry; do not bypass verification
404 NOT_FOUND{resource} was not foundConfirm UUID and selected organization; foreign resources intentionally look absent
400 INVALID_IDENTIFIERidentifier is not a valid UUIDSend canonical UUID text

/healthz can remain 200 while identity is unavailable; it proves process/schema readiness, not authenticated dependencies.

Mailbox cannot be declared, updated or removed#

CodeExact sentence / conditionAction
MAILBOX_NOT_DECLAREDSkarbiec item '<ID>' does not carry skrzynka:mailbox; Skrzynka keeps its mail and no longer polls it (mailbox last_error_code)Tag the item skrzynka:mailbox in Skarbiec or run skrzynka mailbox declare --skarbiec-item <ID> to resume it; otherwise leave it or remove its local mail
MAILBOX_STILL_DECLAREDSkarbiec item '<ID>' still carries skrzynka:mailbox; undeclare the mailbox before removing its local mailRun skrzynka mailbox undeclare <UUID> first
MAILBOX_ALREADY_EXISTSa mailbox already uses this Skarbiec item — the item already backs a mailbox of another organization in this databaseUse the existing mailbox from that organization
CONFIRMATION_REQUIREDmailbox removal requires --confirm (CLI) / mailbox removal requires confirm=true (HTTP)Reinspect scope, then explicitly confirm
MAILBOX_PROFILE_INVALIDskarbiec_item_id must contain non-whitespace characters onlyCorrect the ID
sameemail is required / email is not a valid addressSupply a valid address in the Skarbiec item
sameimap_host is required / smtp_host is requiredComplete the Skarbiec item
same{field} must be a hostname without a URL schemeUse hostname only, no scheme or whitespace
samemail server ports must be nonzeroUse valid nonzero ports
samedisplay_name must not be emptySupply a nonempty label
GMAIL_PROFILE_INVALIDemail is not a valid addressCorrect account delegate --email or account app-password --email

SMTP security accepts only starttls and tls; CLI rejects other values during parsing and JSON rejects an unknown enum.

Gmail app-password connection fails#

CodeExact messageAction
GMAIL_APP_PASSWORD_INPUT_INVALIDGoogle app-specific password could not be read from stdinSupply the app-specific password on stdin; never add it to argv
sameGoogle app-specific password supplied through stdin must be valid UTF-8Supply the text credential directly, not binary data
sameGoogle app-specific password supplied through stdin must not be emptyGenerate or retrieve the account's app-specific password and supply it on stdin
GMAIL_APP_PASSWORD_ITEM_INVALIDselected Skarbiec item does not contain a password credentialIn Skrzynka Desktop select a canonical Skarbiec login or bundle containing username and password; do not select an OAuth item
NOT_FOUNDmailbox was not foundCorrect an explicit --mailbox UUID/address or omit the option to use the existing account-address behavior; this refusal occurs before Gmail authentication or any write
MAILBOX_SELECTOR_AMBIGUOUS<SELECTOR> names <COUNT> mailboxes (<ID>, <ID>); select one by idRetry with the exact mailbox UUID; this refusal occurs before Gmail authentication or any write
GMAIL_IMAP_PASSWORD_REJECTEDGoogle refused IMAP password authentication. The error names the mailbox and Skarbiec item, gives the Weles app-password command for the same organization, and gives skrzynka --organization <ID> account connection --provider gmail --email <address> --bind <loopback-address:port> to measure alternatives. Supply the callback address intended for OAuth; no address is assumed.Run the named Weles command with the account's Skarbiec Google login: it signs the account in, creates an app password named Skrzynka and pipes it to skrzynka --organization {organization} account app-password --provider gmail, which proves it over IMAP, stores it and declares the mailbox in that organization. The refusal does not recommend the OAuth path, because whether that path can complete is a fact about this installation: run account connection and it is measured. This refusal occurs before the CLI writes the item and before either path creates or changes a mailbox
GOOGLE_DELEGATION_NOT_APPLICABLE{address} is a consumer Google account, and domain-wide delegation exists only inside a Workspace domain. No administrator can grant it for this address.Reported by account connection as a refused path, not as a command failure: the exit status stays 0. Its action is weles app-password --provider google --login-role <Skarbiec role the Google login of {address} plays> --organization {organization}, which creates the app password this account needs; no administrator can grant delegation for a consumer address, so no console visit will help
GMAIL_APP_PASSWORD_NOT_STOREDNo mailbox is named {address}, so no stored credential could be authenticated. An app-specific password connects this account without a Workspace administrator or an OAuth client.Reported by account connection as an unproven path: there is nothing to authenticate yet. Connect the account with skrzynka account app-password --provider gmail --email {address} and run the report again

Password LOGIN errors append IMAP LOGIN at <HOST>: <DETAIL>, preserving the observed library response code and explanation without the submitted password. Connection or protocol errors use IMAP_AUTHENTICATION_FAILED rather than claiming a rejected Gmail password; I/O and lost connections are retryable. AUTHENTICATIONFAILED alone does not distinguish an incorrect account password from a revoked or invalid app password. CLI and API return the detail in message; synchronization also stores it in last_error_message.

Skarbiec is unhealthy#

CodeExact messageAction
SKARBIEC_UNAVAILABLESkarbiec could not be started from the configured pathCorrect --skarbiec-bin, install it, and retry
sameSkarbiec metadata listing failedRun the selected Skarbiec version and restore its local authority
SKARBIEC_TIMEOUTSkarbiec did not finish within 15 secondsResolve vault/CLI stall; retry only after it is responsive
SKARBIEC_RESPONSE_TOO_LARGESkarbiec response exceeded the 2 MiB safety limitReduce/correct the selected output
SKARBIEC_RESPONSE_INVALIDSkarbiec returned invalid metadata JSON / Skarbiec returned invalid item JSONUpgrade/fix Skarbiec contract output
SKARBIEC_ITEM_INVALIDselected Skarbiec item is missing, unreadable, or unavailableRestore/select the exact item
sameitem has no canonical kind, item kind must be login or bundle, or item has no canonical fields objectRewrite as canonical skarbiec.item.v2
sameitem field {name} is required and must be textAdd the named text field; never copy it into Skrzynka configuration
SKARBIEC_WRITE_FAILEDGmail authorization could not be sent, persisted, or was rejectedRestore write authority and restart the Gmail connection flow

A false skarbiec_available in status is diagnostic state, not a command failure. Existing local messages remain readable.

Synchronization fails#

CodeExact messageAction
IMAP_UNAVAILABLE (retryable:true)IMAP server could not be reached over TLSVerify host, port, DNS, TLS, and provider availability; note there is no configured IMAP network timeout
IMAP_AUTHENTICATION_FAILEDIMAP authentication was refused; inspect the selected Skarbiec itemCorrect/rotate password or app password in Skarbiec
sameGoogle refused the saved Gmail authorization; reconnect the profileReconnect Gmail
IMAP_INBOX_UNAVAILABLEthe provider did not make INBOX availableRestore provider INBOX access
IMAP_SEARCH_FAILEDIMAP UID search failedInspect provider protocol state and retry
IMAP_FETCH_FAILEDIMAP fetch failed at UID {uid}Retry later; cursor did not commit this failed batch

All-mailbox sync exits 0 and embeds each failure. Inspect every mailboxes[].ok, plus mailbox last_error_code/last_error_message. A raw message over 2 MiB, missing body, or malformed content is counted as skipped; it does not produce a per-message API error.

Reply fails or is ambiguous#

CodeExact messageAction
REPLY_FILE_INVALIDmessage body file could not be readCorrect path/permissions
samemessage body file must be a regular file no larger than 256 KiBUse a regular bounded file
samemessage body file must contain valid UTF-8 textConvert to UTF-8
IDEMPOTENCY_KEY_INVALIDidempotency_key must contain 1 to 200 non-whitespace charactersCorrect the key
IDEMPOTENCY_KEY_REUSEDidempotency key already belongs to a different reply requestNever repurpose a key; generate a new key only for a deliberate new send
REPLY_BODY_INVALIDreply body must not be emptyAdd non-whitespace text
REPLY_BODY_TOO_LARGEreply body exceeds the 256 KiB limitReduce body
MESSAGE_REPLY_TARGET_INVALIDmessage has no valid Reply-To or From addressDo not send; correct source data outside Skrzynka
SMTP_TLS_FAILEDSMTP TLS configuration was rejectedCorrect host/security profile
SMTP_REJECTEDSMTP explicitly rejected the message: {provider refusal}Read the provider's own sentence in the message and fix that condition; a new key is a new send
SMTP_UNCERTAIN / stored REPLY_UNCERTAINSMTP acceptance is uncertain ({provider detail}); inspect provider Sent mail before another attemptInspect provider Sent mail; never auto-resend

After an unclean stop, send was interrupted before terminal SMTP evidence was recorded marks the attempt uncertain; a send task that disappeared while the process kept running records send task stopped before terminal SMTP evidence was recorded. Preserve the database while investigating.

Outbound send fails or is ambiguous#

CodeExact messageAction
MAILBOX_SELECTOR_AMBIGUOUS{selector} names {n} mailboxes ({id}, {id}); select one by idTwo mailboxes carry that address; repeat the call with the exact mailbox id you meant
OUTBOUND_FILE_INVALIDmessage body file could not be readCorrect path/permissions
samemessage body file must be a regular file no larger than 256 KiBUse a regular bounded file
samemessage body file must contain valid UTF-8 textConvert to UTF-8
IDEMPOTENCY_KEY_INVALIDidempotency_key must contain 1 to 200 non-whitespace charactersCorrect the key
IDEMPOTENCY_KEY_REUSEDidempotency key already belongs to a different outbound messageNever repurpose a key; generate a new key only for a deliberate new send
IDEMPOTENCY_CONFLICToutbound message request already existsRead the existing row with skrzynka message outbound or GET /v1/outbound/{id} before doing anything else
OUTBOUND_SUBJECT_INVALIDsubject must contain between 1 and 500 charactersSupply a bounded nonempty subject
OUTBOUND_BODY_INVALIDoutbound body must not be empty / message body must not be emptyAdd non-whitespace text
OUTBOUND_BODY_TOO_LARGEoutbound body exceeds the 256 KiB limit / message body exceeds the 256 KiB limitReduce body
OUTBOUND_RECIPIENT_INVALID{address} is not a valid email address, at least one recipient address is required, outbound message must name at least one recipient, or outbound recipient is not a valid email addressPass bare addresses only; a display name is refused here rather than at the provider
OUTBOUND_MESSAGE_INVALIDoutbound message could not be encoded as an email messageInspect the stored recipients and subject; do not retry with the same key
SMTP_REJECTEDSMTP explicitly rejected the message: {provider refusal}Read the provider's own sentence in the message and fix that condition; a new key is a new send
SMTP_UNCERTAIN / stored OUTBOUND_UNCERTAINSMTP acceptance is uncertain ({provider detail}); inspect provider Sent mail before another attemptInspect provider Sent mail; never auto-resend

Outbound rows left in sending by an unclean stop are recovered on the next database open as uncertain with OUTBOUND_UNCERTAIN and send was interrupted before terminal SMTP evidence was recorded. Skrzynka never resends them. The full contract is on outbound message.

Gmail OAuth and delegation#

CodeExact meaning / recovery
GMAIL_OAUTH_CLIENT_INVALIDOAuth authorization endpoint is invalid/untrusted; replace the Desktop-app client item with canonical endpoints
GMAIL_OAUTH_STATE_MISSING / GMAIL_OAUTH_STATE_INVALIDcallback did not carry the issued state; start a new flow
GMAIL_OAUTH_CODE_INVALIDGoogle returned no valid code; start again
GMAIL_OAUTH_REDIRECT_NOT_REGISTEREDthe OAuth client {client_id} has no loopback redirect URI registered, so Google refused this authorization with redirect_uri_mismatch before showing any consent screen; it was presented {redirect_uri}. Register a loopback redirect URI for that client in the Google Cloud Console, or issue a Desktop app client which accepts any loopback port, and retry
GMAIL_OAUTH_FLOW_CONSUMEDflow is processing/completed or has no pending authorization; poll it or start a new flow
GMAIL_OAUTH_REJECTEDoperator or Google rejected authorization; start again after correcting account policy
GMAIL_REFRESH_TOKEN_MISSINGno durable authorization returned; reconnect and consent again
GMAIL_OAUTH_ACCOUNT_MISMATCHauthorized email differs from selected profile; authorize the exact selected account
GMAIL_AUTHORIZATION_EXPIREDGoogle returned invalid_grant for the saved refresh authorization; reconnect profile
GMAIL_TOKEN_REFRESH_UNAVAILABLE, GMAIL_OAUTH_UNAVAILABLE, GMAIL_IDENTITY_UNAVAILABLE, GOOGLE_TOKEN_UNAVAILABLEGoogle dependency unavailable; retry only when retryable:true
GMAIL_OAUTH_RESPONSE_INVALID, GMAIL_TOKEN_RESPONSE_INVALIDGoogle returned a missing/invalid field; do not retry blindly; inspect provider status and version
GOOGLE_DELEGATION_NOT_GRANTEDfollow the returned message exactly: The Workspace admin has not granted domain-wide delegation to this service account. Open https://admin.google.com/ac/owl/domainwidedelegation, add client ID {client_id} with scope https://mail.google.com/, then retry. Skrzynka never performs that grant; it is an action a Workspace administrator takes in the admin console
GOOGLE_DELEGATION_REJECTEDverify the address is an active user in the Workspace domain that granted that client ID

The browser callback deliberately shows only generic success/failure HTML. Obtain the exact code from the authenticated flow-status resource or CLI error, never from token-bearing browser URLs.