Overview
This page lists every error shipd returns and how to fix it:shipd exit codes and error codes, the control-plane API error envelope, edge API statuses, and the errors the shipdit SDK throws or reports to onError. For symptoms such as “my flag isn’t updating”, start with Troubleshooting.
CLI exit codes
| Code | Meaning | Common causes | Fix |
|---|---|---|---|
0 | Success | Push applied or had nothing to do; diff found no drift | — |
1 | General error | Not authenticated; 403 forbidden; invalid shipd.config.ts; project or environment not found; confirmation declined or no TTY; unreachable API; command usage error | Read the Error: line. See CLI error codes. |
2 | Drift | shipd diff --exit-code found creates, updates, or remote-only flags | Run shipd push, or shipd pull if the server is right. |
3 | Conflict (409) | A flag changed on the server after the plan; a pending approval request already exists for the flag and environment; a definition change touches a project with an approval policy | See Why did shipd push return a conflict? |
4 | Unlinked | shipd diff --exit-code found unlinked flags. Current servers do not report them. | Treat like 2. |
5 | Pending approval | shipd push queued one or more changes for review. They are not live. | Ask a reviewer to approve the request. See Approvals. |
CLI error codes
Human output printsError: <message> on stderr. With --json, stderr gets:
{ "error": { "code": "validation_error", "message": "Environment is required. Pass --env, set SHIPD_ENV, or use a management key / service-account token bound to an environment." } }
| Code | Message | Fix |
|---|---|---|
validation_error | Project is required. Pass --project, set SHIPD_PROJECT, or use a management key / service-account token bound to a project. | Pass --project or bind the credential to a project. |
validation_error | Environment is required. Pass --env, set SHIPD_ENV, or use a management key / service-account token bound to an environment. | Pass --env. |
validation_error | Organization mismatch: API key is bound to "<org>", but --org/SHIPD_ORG is "<org>". … | Remove --org / SHIPD_ORG, or use a credential for that organization. |
validation_error | Failed to load config <path>: <message> | Fix the TypeScript or defineFlags error in shipd.config.ts. See validation rules. |
validation_error | shipd.config.ts must export defineFlags(...) result | export default defineFlags({ … }). |
validation_error | Invalid flag definitions: <path>: <message> | Fix the named flag. |
validation_error | Push cancelled (pass --yes or --ci to apply non-interactively) | Answer y, or pass --yes in scripts. |
validation_error | Archive cancelled | Answer y, or pass --yes. |
validation_error | Variant flags require --variants (e.g. --variants control,treatment) | shipd flags create with --type variant needs --variants. |
validation_error | --variants is only valid with --type variant (got --type <type>) | Drop --variants. |
validation_error | Unsupported flag type: <type> | Use boolean, variant, number, or json. |
validation_error | Command usage error (unknown option, missing argument) | Run shipd <command> --help. |
not_found | Config file not found: <path> | Run from the project root or pass --flags-config. |
not_found | Project not found: <slug> / Environment not found: <slug> | Check shipd projects list / shipd environments list, and the credential’s binding. |
unauthenticated | Device authorization timed out / Device authorization failed | Run shipd login again and approve within the time limit. |
unauthenticated | Device authorization did not return a management API key. Approve org, project, and environment in the browser. | Choose all three in the browser. |
internal | Any unexpected error, such as a network failure | Check --api-url / SHIPD_API_URL. In CI it must be https://api.useshipd.com. |
Control-plane API errors
Every non-success response fromhttps://api.useshipd.com/api/v1 has this envelope. Stack traces are never returned.
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"requestId": "req_01",
"details": {}
}
}
| Status | Code | Meaning | CLI exit |
|---|---|---|---|
| 401 | unauthenticated | Credential is missing, invalid, expired, revoked, or has no active organization. | 1 |
| 403 | forbidden | The principal cannot perform the action. Includes environment-bound credentials attempting flag.create, flag.update, or flag.archive. | 1 |
| 403 | quota_exceeded | An SDK or bulk quota is exhausted. | 1 |
| 404 | not_found | The resource is missing or hidden from this principal. | 1 |
| 409 | conflict | Version, uniqueness, approval, or idempotency state conflicts. | 3 |
| 413 | payload_too_large | A documented batch or body limit was exceeded. | 1 |
| 422 | validation_error | The request does not match its schema. | 1 |
| 429 | rate_limited | Retry after the delay in Retry-After. | 1 |
| 500 | internal | Unexpected failure. Report requestId to support. | 1 |
| 503 | tenant_migrating, tenant_unavailable | Organization data is temporarily unavailable. Retry safely. | 1 |
Push conflict messages
shipd push returns these 409 conflict messages. None of them applies any part of the push.
| Message | Cause |
|---|---|
Flag "<key>" changed remotely (expected version <n>, remote <m>). Pull and retry, or pass --force. | The flag definition changed after the plan. |
Flag "<key>" environment state changed remotely (expected environment version <n>, …). Pull and retry, or pass --force. | The target environment’s state (fallthrough, rules, enabled) changed after the plan. |
A pending approval request already exists for this flag in this environment | An earlier push or dashboard edit is waiting for review. |
This operation affects an environment protected by approval policy and cannot be submitted for review. No changes were applied. | A create, archive, or definition change in a project with an approval policy. |
Flag "<key>" was created concurrently. Pull and retry. | Someone created the same key during your push. |
Edge API statuses
Returned byhttps://edge.useshipd.com/sdk/v1. The SDK handles all of them without throwing.
| Status | Code | Meaning | SDK behavior |
|---|---|---|---|
| 304 | — | Snapshot unchanged since the sent ETag | Keeps the current snapshot. |
| 401 | — | SDK key is invalid or revoked, or the credential is not an SDK key | Keeps the last good snapshot, else defaults. Reports snapshot fetch failed: HTTP 401. |
| 403 | — | The SDK key lacks the scope for the route (for example, identity:read with a client key) | Reports the failure to onError. |
| 413 | payload_too_large | Events batch over 100 events or 256 KiB | Reports events flush failed: HTTP 413. |
| 422 | validation_error | Malformed events batch | Reports events flush failed: HTTP 422. |
| 429 | — | Over the per-key rate limit (6,000 requests per minute at the edge rate limiter). Retry-After is set. | Keeps the last good snapshot, reports snapshot fetch failed: HTTP 429, and retries on the next poll. |
SDK errors
Thrown errors
Only these throw. Evaluation methods (isEnabled, getVariant, getNumber, getJson) never throw, and ready() never rejects for network errors.
| Message | Thrown by | Fix |
|---|---|---|
shipdit: sdkKey is required | createClient | Pass sdkKey. Check that the environment variable is set where the code runs. |
shipdit: `endpoint` was removed… | createClient | Remove endpoint. For shipd development and tests only, use overrides.unsafeEndpoint. |
shipdit: `streamEndpoint` was removed… | createClient | Replace it with stream: true. To use another gateway, set overrides.streamEndpoint. |
shipdit: overrides.unsafeEndpoint must… / shipdit: overrides.streamEndpoint must… | createClient | Pass an absolute URL (https://…; the stream also accepts wss://) without credentials, a query, or a fragment. |
shipdit: identify requires userId | identify | Pass a non-empty userId. Use reset() on logout instead of identify({ userId: "" }). |
shipdit: useShipdClient must be used within ShipdProvider | useShipdClient and the hooks built on it | Render the component inside ShipdProvider. |
onError messages
Background failures go to theonError option as Error objects. The client keeps serving the last good snapshot, a cached or bootstrapped snapshot, or defaults.
| Message | Meaning |
|---|---|
snapshot fetch failed: HTTP <status> | The edge returned a non-2xx status other than 304. 401 means the key is invalid or revoked. |
snapshot fetch timed out | No response within fetchTimeoutMs (default 10,000 ms). |
snapshot fetch aborted | The request was cancelled, usually by close(). |
Unsupported snapshot schema version <n>; SDK supports version <m> | The snapshot format is newer than this SDK. Upgrade shipdit. |
malformed snapshot ignored | The response was not a valid snapshot. |
shipdit: snapshot version <v> still below notified <n> after 5 attempts | A realtime update announced a version the edge did not serve after 5 fetches. The next poll catches up. |
fetch is not available in this runtime | No global fetch. Pass the fetch option. |
identity fetch failed: HTTP <status> / identity fetch timed out / identity fetch aborted | Loading remote traits after identify() failed. Local evaluation continues with session traits. |
stream ticket mint failed: HTTP <status> | Minting a WebSocket ticket failed. Polling continues. |
stream ticket mint failed: invalid body / : missing ticket / : missing expiry | The ticket response was malformed. Polling continues. |
stream ticket mint timed out / stream ticket mint aborted | Ticket request timed out or was cancelled. |
shipdit: WebSocket is not available in this runtime | stream is true but there is no WebSocket. On Node 20, pass the WebSocket option (for example, from ws) or omit stream. Polling continues. |
shipdit stream: <code>: <message> | The WebSocket gateway sent an error. |
shipdit stream: unauthenticated | The gateway rejected the key. The stream closes; polling continues. |
shipdit stream: unsupported_protocol | The gateway does not support this SDK’s stream protocol. Upgrade shipdit. |
Unsupported WebSocket protocol version <n>; SDK supports version <m> | The gateway uses a newer stream protocol. Upgrade shipdit. |
shipdit stream: unsupported binary frame / shipdit stream: malformed server message | The gateway sent a frame the SDK cannot read. The stream closes; polling continues. |
events flush failed: HTTP <status> / events flush timed out / events flush aborted | Sending diagnostic events failed. Evaluation is unaffected. |
// src/flags.ts
import { createClient } from "shipdit";
import type { FlagDefinitions } from "./generated/flags";
export const shipd = createClient<FlagDefinitions>({
sdkKey: process.env.SHIPD_SDK_KEY!,
defaults: { "new-checkout": false },
onError: (error) => console.warn("[shipd]", error),
});
// Check New and Ready errors, then inspect LastError for background request failures.
client, err := shipd.New(shipd.Options{
SDKKey: os.Getenv("SHIPD_SDK_KEY"),
Defaults: map[string]any{"new-checkout": false},
})
if err != nil { return err }
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := client.Shutdown(ctx); err != nil { log.Printf("shipd events: %v", err) }
}()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := client.Ready(ctx); err != nil { log.Printf("using defaults: %v", err) }
if err := client.LastError(); err != nil { log.Printf("shipd request: %v", err) }