Troubleshooting connection issues
The 10 connection-state failure modes the syncropel.com workspace can hit, with remediation per state and a common-error-code reference.
When the Studio workspace can't reach an instance, the connection is in exactly one of ten states. Every state has a clear UI signal and a one-line fix. This page maps each state to its symptom and remedy so you can resolve issues without opening DevTools.
Quick diagnose
For a live snapshot of what the browser knows about its connection,
visit /diagnose. The
page shows:
- Current connection state + active credential
- Recent API errors ring buffer (last 20 in this session)
- Browser feature checklist (IndexedDB, Web Crypto, Service Worker)
- Reset-pairing button (forgets the credential, redirects to re-pair)
- A copy-paste JSON snapshot for support tickets
The banner at the top of the workspace always links here ("diagnose").
Failure modes
1. Instance offline
Symptom: Banner says "Instance offline". The dashboard shows the
offline panel with a spl serve hint.
Why: /health is timing out or refused on every candidate URL.
Fix: spl serve in your terminal. Verify with
curl http://localhost:9100/health. If the instance was running but
stopped, spl serve --status will tell you whether the PID file is
orphaned.
2. Pair required
Symptom: Banner says "Pair required". The dashboard shows the pairing screen.
Why: Instance has auth.required = true (the v0.16+ default) and the
browser has no stored credential for this instance's DID.
Fix (fastest, no CLI): if you kept a key to this place, open + add a connection and paste the whole line; you are back in as the same person (Keeping your access). Members and guests without the CLI should always start here.
Fix (operator with the CLI): Run spl pair --device browser-<name>. The
CLI's first line of output is now a single clickable URL of the form
https://syncropel.com/pair#<encoded-payload>. Click it (or copy
into your browser). The page decodes the link, verifies the token,
stores the credential, and redirects you to /tasks — all
without typing or scanning anything. The token rides in the URL fragment
(after the #), which never reaches any server: it stays inside your
browser.
Fix (older clients or no clickable URL): paste the QR or payload
into the pairing card. Add --scopes admin if you want access to
admin-only routes.
3. Pairing invalid (token_invalid)
Symptom: Banner says "Pairing invalid". The dashboard shows the expired-pairing screen.
Why: Stored credential is rejected by the instance — usually because the instance's data was nuked since pairing (instance DID changed) or the token was revoked.
Fix: if you kept a key, paste it into + add a connection first; a revoked or expired bearer does not spend your key, and you come back under the same grant. Otherwise open /diagnose and click "Reset pairing", then re-pair
with spl pair --device browser-<name> and click the printed URL.
4. Limited (scope insufficient) — partial_access
Symptom: Banner is amber and says "Limited (scope insufficient)".
Some panels render; others show an inline ApiErrorPanel saying the
token is missing a specific scope.
Why: /v1/health succeeds + the credential is valid, but two or
more /v1/* endpoints have returned 403 SCOPE_FORBIDDEN in the
last 60 seconds. The token is real but doesn't have the scopes this
view needs.
Fix: Re-pair with broader scopes. The ApiErrorPanel shows the
exact command to copy:
spl pair --device browser --url <instance-url> --scopes adminOr scope it more narrowly to the resources you actually need.
5. Browser blocked (PNA / CORS)
Symptom: Banner says "Browser blocked". Dashboard renders the
BrowserBlocksLocal panel with a link to the pre-flight diagnostic.
Why: Chrome's Private Network Access policy blocks https://syncropel.com
from fetching http://127.0.0.1 without an explicit ACK from the
instance. Older instance builds didn't ACK the PNA preflight.
Fix: Upgrade the instance: curl -sSf https://get.syncropic.com/spl | sh && spl stop && spl serve. Or grant PNA permission
manually in chrome://settings/content/insecureprivatenetwork.
6. Multiple instances reachable
Symptom: Banner says "Connecting…" and the InstanceChooser card
appears with a list of detected instances.
Why: 2+ candidate URLs returned 200 from /health (loopback +
LAN + manually entered URL). The user has to pick one before the
state machine can classify it.
Fix: Click the instance you want. The state machine remembers it as the preferred URL via IndexedDB, so subsequent loads skip the chooser.
7. Mobile cold start
Symptom: On a phone (no instance-capable host on this device),
banner shows the MobileLocalWelcome cards (QR pair + URL paste +
browse).
Why: Phones cannot run a Syncropel instance. The localhost probe baseline is replaced with explicit pairing affordances.
Fix: Pair via QR from a desktop / laptop instance. Or paste a URL to an instance you've reached on your LAN.
8. Connected (insecure)
Symptom: Amber banner says "DEV MODE · auth bypassed".
Why: Instance was started with auth.required = false, typically
during dev with --insecure-localhost or before v0.16's default
flip. Functional but not safe to expose.
Fix: Restart with auth.required = true (the default in current
releases) once you've finished dev work. Or leave as-is for ephemeral
test instances on --port 9200 --memory.
9. Connected (secure)
Symptom: Green dot. Banner says Connected to <instance-label>.
Why: Healthy. /health 200 + credential injected + recent /v1/*
queries succeeded.
Fix: None — this is the happy path.
10. Connecting / generic unreachable
Symptom: Banner says "Connecting…" and the dashboard renders a generic unreachable panel.
Why: One of the fall-through states — remote_unreachable,
cors_blocked, daemon_too_old, daemon_error, token_insufficient.
These don't have dedicated Phase 1 surfaces yet.
Fix: Open /diagnose → "Recent API errors" to see the
underlying code + message from the instance. Most often it's
either a stale instance (upgrade spl serve), a missing CORS allow
entry (spl config auth-set-cors-origins ...), or a network glitch.
Common error codes (HTTP 4xx)
The instance returns a structured error envelope on every 4xx response:
{
"object": "error",
"code": "SCOPE_FORBIDDEN",
"message": "token missing required scope 'admin' for this route",
"required_scope": "admin"
}The /local workspace surfaces these in <ApiErrorPanel> with a
copy-paste suggested fix per code.
| Code | Meaning | Remediation |
|---|---|---|
AUTH_REQUIRED | No bearer token sent | spl pair --device browser-<name> and retry |
AUTH_INVALID | Token rejected (expired, revoked, or instance changed) | Reset pairing on /diagnose, then re-pair |
SCOPE_FORBIDDEN | Token valid but missing required scope | Re-pair with --scopes admin (or narrower scope per use) |
NOT_FOUND | Endpoint not available on this instance version | Upgrade the instance or stop using this view |
CORS | Origin not on instance's CORS allowlist | spl config auth-set-cors-origins https://syncropel.com http://localhost:3000 && spl stop && spl serve |
PNA | Browser blocked the cross-origin → loopback fetch | Upgrade the instance or grant PNA permission in browser |
NETWORK | Couldn't reach the instance | spl serve --status and verify with curl http://localhost:9100/health |
TIMEOUT | Request timed out | Instance under load or unreachable; retry; check spl doctor |
INTERNAL | Instance returned 5xx | Inspect ~/.syncro/logs/spl.log; file an issue if reproducible |
See also
spl doctor— top-down instance diagnosticspl pair— pairing flow + scope selection- Runbook section Backup discipline
What spl doctor tells you
Every row spl doctor prints today, quoted as it prints, with what a warning on it means and what to do. Run it first when something feels wrong, after an upgrade, and before filing a report.
Instance Lifecycle
Provision an instance, get past the first-run auth chicken-and-egg, run a backup discipline that actually saves you, watch the right health probes, and tear down cleanly. The end-to-end recipe for self-hosted operators.