Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Troubleshooting

Fast-path fixes for the errors we see most often. Skim by symptom.

Client can't connect to the WebSocket#

Symptom. Your client library returns an HTTP 401 / 403 during upgrade, or the connection closes immediately with code 4401 / 4403.

  • 401 or 4401 — auth failed. Bearer token missing, expired, or malformed. Also fires when you passed an sgk_ API key but the tenant has revoked it.
  • 4403 — auth resolved but the tenant context is missing (rare; means the caller has no tenant assigned) or the SCSG endpoint got a non-service principal.
  • Double-check ?token=… vs Authorization: Bearer …. Both work; only pass one.
  • SCAIGRID_AUTH_DISABLED=true on the server disables all auth. If you're running local dev and can't connect, that's likely the missing flag.

device_id query param required#

Symptom. WS close code 4403 with that reason on the SCWP endpoint.

The ?device_id=… query parameter is required on every scaiclip/1 connection. It must be either a 32-char hex string (16 raw bytes) OR a ScaiKey nanoid (dev_XYZ…).

E_BAD_SIG on clip.publish#

  • Signature was made over the wrong subset of envelope keys. Signature input is canonical CBOR of keys 1-14 only. Keys 3 (seq) and 7 (grid_ts) are grid-assigned; do not include them.
  • Sender sig_pub doesn't match what ScaiKey has on file. Common cause: you rotated device keys client-side but didn't call POST /me/devices/{id}/keys on ScaiKey.
  • Endian mismatch in the CBOR encoder. Canonical CBOR means definite-length maps sorted by canonical CBOR key ordering. If your library reorders map keys, signatures will fail.

E_WRAPS_INCOMPLETE on ONE_SHOT publish#

Symptom. You published a ONE_SHOT clip and got E_WRAPS_INCOMPLETE { missing: [dev_xyz, …] }.

The grid requires one-shot wraps for every current member device at publish time. If a new device onboarded between when your client fetched channel.devices and when you published, that device is missing a wrap.

Fix: re-query channel.devices, generate a fresh wrap for each missing device, retry clip.publish.

E_ALREADY_CLAIMED#

Someone else claimed the one-shot before you. detail.claimed_by names the winning subject; detail.at is the ms timestamp.

Not an error condition — this is how one-shot works. Your UI should show the user "already claimed by X" rather than retry.

E_RATE_LIMITED#

You're publishing / reserving / opening sessions faster than the token bucket allows. detail.retry_after_ms says when to try again.

Default rates:

  • clip.publish — 30/s per device with 60-token burst.
  • scratch.reserve — 2/s per user with 10-token burst.
  • SCSG session.open — 5/s per (tenant, service_id) with 20-token burst.
  • Public link mint — 30/min per tenant with 60-burst.

If you're bumping the limit on legitimate load, ask for an uplift.

E_QUOTA_EXCEEDED on scratch#

Symptom. scratch.reserve fails with detail = {scope: "user" | "tenant", used, limit, additional}.

  • Delete old scratch objects (scratch.delete) — list first to see what you have, and check the oldest_reclaimable hint in the error detail.
  • Ask the tenant admin to raise the quota (setting: scaiclip_scratch_quota_user_bytes / _tenant_bytes).
  • Consider ref_origin mode for the specific clip — bypasses scratch storage entirely.

E_ORIGIN_OFFLINE#

You tried to media.fetch a ref_origin clip while the origin device isn't connected. There's no queuing — ref_origin is deliberately ephemeral.

Surface a UX like "sender is offline; ask them to reopen ScaiClip". If the content matters over the origin's presence, the sender should have published with ref_scratch instead.

E_FORBIDDEN — SCSG session refuses to open#

Possible causes, in order of frequency:

  1. Tenant hasn't opted your service in. Check mod_scaiclip_tenant_policy.allow_scsg_<service_id>.
  2. You requested CLAIM capability but tenant hasn't opted in for one-shot consumption. That gate is on allow_agent_claim.
  3. Your client_id doesn't map to a registered service_id per the SCSG registry. Prefix your client_id with cli_<service_id>_.

Error detail includes service_id and tenant_id for triage.

E_NO_ONLINE_DEVICE#

You're trying to open an SCSG session but the user has no device connected. Grid needs at least one online T1 to consent-prompt.

Client-side UX: prompt the user to open their ScaiClip client first, then retry.

The link is dead. Possibilities:

  • Expired (expires_at in the past).
  • Revoked (owner or via revoke_token).
  • Download limit reached.
  • Underlying scratch object was reclaimed (rare — happens if scratch idle-TTL fires before public expiry).

The 410 is returned in constant time; the specific reason isn't disclosed to the anonymous fetcher. Owners can look up the token record via the API to see what happened.

Retention engine isn't sweeping#

Symptom. Sensitive clips outlive their TTL by many seconds, or expired scratch objects stick around.

  • Confirm the retention engine started. Log line scaiclip_retention_started should appear in module init.
  • Check is_running — all four loops (sensitive, normal, scratch, SCSG) must be alive. A crash in one loop stops that specific sweep but not the others.
  • If the process just restarted, the first sweep runs _sensitive_cadence_s seconds after start (default 15 s), not immediately.

Presence heartbeats aren't reaching ScaiKey#

  • Confirm PresenceHeartbeatPusher started (log line scaiclip_presence_started).
  • Check the shared ScaiKey client is configured — SCAICLIP_SCAIKEY_URL or SCAIKEY_BASE_URL must be set.
  • Test the heartbeat batch endpoint directly with a service token. If the platform returns 401/403, the ScaiKey OAuth scopes on the ScaiGrid application don't include devices:heartbeat.

Audit rows aren't landing#

  • Audit is best-effort — it never breaks the calling path. Failures log as scaiclip_audit_emit_failed.
  • Common cause: audit_log table missing. Run alembic upgrade head on the ScaiGrid schema, not just the module schema.
  • Filter with action LIKE 'scaiclip.%' in /v1/audit queries.

Where to look next#

  • Every action lands in audit_log with matching resource_type / resource_id. Filter by status = 'DENIED' for rejections.
  • Structured log lines with prefix scaiclip_ cover retention sweeps, presence pushes, webhook receipts, and SCSG lifecycle. Grep the ScaiGrid pod logs.
  • Metrics on RetentionEngine.expired_sensitive_total / _normal_total / reclaimed_scratch_total / expired_scsg_total counters expose sweep health.
  • The error codes page maps each E_* to what it means and what to do.

If none of the above matches your symptom, open an issue with:

  • Wire dump of the failing frame (both directions).
  • Server-side scaiclip_ log lines around the failure.
  • Approximate wall-clock at time of failure so we can correlate with audit rows.
Updated 2026-07-12 00:26:08 View source (.md) rev 5