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 message | Meaning | Action |
|---|---|---|
NON_LOOPBACK_BIND_REFUSED — Skrzynka serves only loopback addresses | --bind IP is not loopback | Use 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 seconds | invalid process interval | Choose 15–86400 |
DATABASE_SCHEMA_UNSUPPORTED — database schema {n} is not supported by this build (expected at most 3) | database is newer than this binary | Stop all processes; use a compatible build or restore the stopped-service pre-upgrade backup |
INTERNAL_ERROR — local state directory could not be created | parent path cannot be created | Fix path ownership/permissions or choose --database elsewhere |
INTERNAL_ERROR — loopback API address could not be bound | port/address is occupied or unavailable | Stop the conflicting process or choose another loopback port |
INTERNAL_ERROR — central identity URL is invalid / central identity configuration is incomplete | environment is malformed or not HTTPS | Correct 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/code | Exact message | Action |
|---|---|---|
401 AUTHENTICATION_REQUIRED | a valid Wisent session is required | Supply Authorization: Bearer ... and a valid X-Wisent-Organization-ID; refresh an expired session |
403 ORGANIZATION_ACCESS_DENIED | the signed-in account does not belong to the selected organization | Select an organization in the verified user's membership |
502 IDENTITY_UNAVAILABLE (retryable:true) | central identity verification is unavailable | Restore identity connectivity/configuration, then retry; do not bypass verification |
404 NOT_FOUND | {resource} was not found | Confirm UUID and selected organization; foreign resources intentionally look absent |
400 INVALID_IDENTIFIER | identifier is not a valid UUID | Send 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#
| Code | Exact sentence / condition | Action |
|---|---|---|
MAILBOX_NOT_DECLARED | Skarbiec 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_DECLARED | Skarbiec item '<ID>' still carries skrzynka:mailbox; undeclare the mailbox before removing its local mail | Run skrzynka mailbox undeclare <UUID> first |
MAILBOX_ALREADY_EXISTS | a mailbox already uses this Skarbiec item — the item already backs a mailbox of another organization in this database | Use the existing mailbox from that organization |
CONFIRMATION_REQUIRED | mailbox removal requires --confirm (CLI) / mailbox removal requires confirm=true (HTTP) | Reinspect scope, then explicitly confirm |
MAILBOX_PROFILE_INVALID | skarbiec_item_id must contain non-whitespace characters only | Correct the ID |
| same | email is required / email is not a valid address | Supply a valid address in the Skarbiec item |
| same | imap_host is required / smtp_host is required | Complete the Skarbiec item |
| same | {field} must be a hostname without a URL scheme | Use hostname only, no scheme or whitespace |
| same | mail server ports must be nonzero | Use valid nonzero ports |
| same | display_name must not be empty | Supply a nonempty label |
GMAIL_PROFILE_INVALID | email is not a valid address | Correct 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#
| Code | Exact message | Action |
|---|---|---|
GMAIL_APP_PASSWORD_INPUT_INVALID | Google app-specific password could not be read from stdin | Supply the app-specific password on stdin; never add it to argv |
| same | Google app-specific password supplied through stdin must be valid UTF-8 | Supply the text credential directly, not binary data |
| same | Google app-specific password supplied through stdin must not be empty | Generate or retrieve the account's app-specific password and supply it on stdin |
GMAIL_APP_PASSWORD_ITEM_INVALID | selected Skarbiec item does not contain a password credential | In Skrzynka Desktop select a canonical Skarbiec login or bundle containing username and password; do not select an OAuth item |
NOT_FOUND | mailbox was not found | Correct 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 id | Retry with the exact mailbox UUID; this refusal occurs before Gmail authentication or any write |
GMAIL_IMAP_PASSWORD_REJECTED | Google 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_STORED | No 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#
| Code | Exact message | Action |
|---|---|---|
SKARBIEC_UNAVAILABLE | Skarbiec could not be started from the configured path | Correct --skarbiec-bin, install it, and retry |
| same | Skarbiec metadata listing failed | Run the selected Skarbiec version and restore its local authority |
SKARBIEC_TIMEOUT | Skarbiec did not finish within 15 seconds | Resolve vault/CLI stall; retry only after it is responsive |
SKARBIEC_RESPONSE_TOO_LARGE | Skarbiec response exceeded the 2 MiB safety limit | Reduce/correct the selected output |
SKARBIEC_RESPONSE_INVALID | Skarbiec returned invalid metadata JSON / Skarbiec returned invalid item JSON | Upgrade/fix Skarbiec contract output |
SKARBIEC_ITEM_INVALID | selected Skarbiec item is missing, unreadable, or unavailable | Restore/select the exact item |
| same | item has no canonical kind, item kind must be login or bundle, or item has no canonical fields object | Rewrite as canonical skarbiec.item.v2 |
| same | item field {name} is required and must be text | Add the named text field; never copy it into Skrzynka configuration |
SKARBIEC_WRITE_FAILED | Gmail authorization could not be sent, persisted, or was rejected | Restore 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#
| Code | Exact message | Action |
|---|---|---|
IMAP_UNAVAILABLE (retryable:true) | IMAP server could not be reached over TLS | Verify host, port, DNS, TLS, and provider availability; note there is no configured IMAP network timeout |
IMAP_AUTHENTICATION_FAILED | IMAP authentication was refused; inspect the selected Skarbiec item | Correct/rotate password or app password in Skarbiec |
| same | Google refused the saved Gmail authorization; reconnect the profile | Reconnect Gmail |
IMAP_INBOX_UNAVAILABLE | the provider did not make INBOX available | Restore provider INBOX access |
IMAP_SEARCH_FAILED | IMAP UID search failed | Inspect provider protocol state and retry |
IMAP_FETCH_FAILED | IMAP 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.
Outbound send fails or is ambiguous#
| Code | Exact message | Action |
|---|---|---|
MAILBOX_SELECTOR_AMBIGUOUS | {selector} names {n} mailboxes ({id}, {id}); select one by id | Two mailboxes carry that address; repeat the call with the exact mailbox id you meant |
OUTBOUND_FILE_INVALID | message body file could not be read | Correct path/permissions |
| same | message body file must be a regular file no larger than 256 KiB | Use a regular bounded file |
| same | message body file must contain valid UTF-8 text | Convert to UTF-8 |
IDEMPOTENCY_KEY_INVALID | idempotency_key must contain 1 to 200 non-whitespace characters | Correct the key |
IDEMPOTENCY_KEY_REUSED | idempotency key already belongs to a different outbound message | Never repurpose a key; generate a new key only for a deliberate new send |
IDEMPOTENCY_CONFLICT | outbound message request already exists | Read the existing row with skrzynka message outbound or GET /v1/outbound/{id} before doing anything else |
OUTBOUND_SUBJECT_INVALID | subject must contain between 1 and 500 characters | Supply a bounded nonempty subject |
OUTBOUND_BODY_INVALID | outbound body must not be empty / message body must not be empty | Add non-whitespace text |
OUTBOUND_BODY_TOO_LARGE | outbound body exceeds the 256 KiB limit / message body exceeds the 256 KiB limit | Reduce 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 address | Pass bare addresses only; a display name is refused here rather than at the provider |
OUTBOUND_MESSAGE_INVALID | outbound message could not be encoded as an email message | Inspect the stored recipients and subject; do not retry with the same key |
SMTP_REJECTED | SMTP 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_UNCERTAIN | SMTP acceptance is uncertain ({provider detail}); inspect provider Sent mail before another attempt | Inspect 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#
| Code | Exact meaning / recovery |
|---|---|
GMAIL_OAUTH_CLIENT_INVALID | OAuth authorization endpoint is invalid/untrusted; replace the Desktop-app client item with canonical endpoints |
GMAIL_OAUTH_STATE_MISSING / GMAIL_OAUTH_STATE_INVALID | callback did not carry the issued state; start a new flow |
GMAIL_OAUTH_CODE_INVALID | Google returned no valid code; start again |
GMAIL_OAUTH_REDIRECT_NOT_REGISTERED | the 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_CONSUMED | flow is processing/completed or has no pending authorization; poll it or start a new flow |
GMAIL_OAUTH_REJECTED | operator or Google rejected authorization; start again after correcting account policy |
GMAIL_REFRESH_TOKEN_MISSING | no durable authorization returned; reconnect and consent again |
GMAIL_OAUTH_ACCOUNT_MISMATCH | authorized email differs from selected profile; authorize the exact selected account |
GMAIL_AUTHORIZATION_EXPIRED | Google returned invalid_grant for the saved refresh authorization; reconnect profile |
GMAIL_TOKEN_REFRESH_UNAVAILABLE, GMAIL_OAUTH_UNAVAILABLE, GMAIL_IDENTITY_UNAVAILABLE, GOOGLE_TOKEN_UNAVAILABLE | Google dependency unavailable; retry only when retryable:true |
GMAIL_OAUTH_RESPONSE_INVALID, GMAIL_TOKEN_RESPONSE_INVALID | Google returned a missing/invalid field; do not retry blindly; inspect provider status and version |
GOOGLE_DELEGATION_NOT_GRANTED | follow 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_REJECTED | verify 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.