synx-connector
v0.4.3
Published
Synx Connector — the local CLI/daemon that bridges a coding agent (Claude Agent SDK or OpenAI Codex SDK) on your machine to a Synx workspace over an authenticated WebSocket control channel.
Maintainers
Readme
synx-connector
The Synx Connector — a local CLI/daemon that bridges a coding agent (the Claude Agent SDK) running on your machine to a Synx workspace over an authenticated WebSocket control channel. People in your workspace can launch and steer AI coding-agent sessions rooted at folders you have explicitly registered on your device; the full transcript streams back to Synx for live viewing and resume.
The connector executes code on your machine. It only ever runs an agent in a folder you registered locally (it does not trust a path the server sends), behind a permission model you control, and you can revoke a device at any time.
The connector is a paid-tier feature — the workspace owner's plan must include it, or a launch is refused with a clear "upgrade required" message.
Install
npm install -g synx-connectorRequires Node.js ≥ 20. Linux, macOS, and Windows are all supported.
Quick start
# 1. Authenticate this machine (opens a browser; OAuth 2.1 + PKCE).
synx-connector login
# 2. Register a folder into a workspace (prints a consent prompt you must accept).
synx-connector register /path/to/project --workspace my-team
# 3. Run the daemon in the foreground…
synx-connector run
# …or install it as a background service (systemd user unit / launchd agent).
synx-connector service installThen open the Machines page in the Synx web app: your device and its registered folders appear there, and you can launch a session on any folder.
Coding agent: Claude or Codex
The connector spawns a coding agent on your machine to run each session. It
can drive either the Claude Agent SDK
(the default) or OpenAI Codex (via the
@openai/codex-sdk). The
choice is install-level, made once by the operator of this machine.
This is a different surface from
connecting-codex.md. That guide covers Codex as an MCP client — pointing Codex at Synx's/mcpendpoint so Codex can drive the tracker. This section is about Codex as the coding agent the connector spawns to do work in a registered folder. The two are unrelated: you can use either, both, or neither.
Selecting the agent
Set the SYNX_CONNECTOR_AGENT environment variable in the daemon's
environment:
| Value | Agent |
| --------------- | -------------------------------------- |
| unset (default) | claude — the Claude Agent SDK |
| claude | the Claude Agent SDK |
| codex | OpenAI Codex (via @openai/codex-sdk) |
The value is case-insensitive; an unset or unrecognized value falls back to
claude, so a typo never silently switches agents and an existing install is
unchanged.
This selection is per connector install, not per session — it is read from
the daemon's environment at startup, and the wire protocol is unchanged (a
session.start carries no agent field, so the server never dictates which agent
runs locally). A per-folder override is a documented future follow-on and is
not shipped: today one machine runs one agent for every folder it hosts.
Set it wherever the daemon reads its environment. For the background service,
that means the unit/plist/task environment — for example, in the systemd user
unit add it under [Service]:
[Service]
Environment=SYNX_CONNECTOR_AGENT=codex(then systemctl --user daemon-reload && systemctl --user restart synx-connector.service).
For a foreground synx-connector run, export it in the shell first:
export SYNX_CONNECTOR_AGENT=codex
synx-connector runRunning Codex — prerequisites
To run the connector with SYNX_CONNECTOR_AGENT=codex, the machine needs:
The
codexCLI available. It ships bundled with the@openai/codex-sdkdependency the connector installs — you do not installcodexseparately. The connector still needs Node.js ≥ 20 as usual (the bundledcodexCLI itself requires Node 18+, which that satisfies), and the daemon'sPATHmust let the CLI resolve (the background-service units already seed an explicitPATHfor this reason).A Codex credential provisioned on the machine, one of:
- ChatGPT login — run
codex login, which writes a token to~/.codex/auth.json; or - an API key in the daemon's environment:
OPENAI_API_KEYorCODEX_API_KEY.
The daemon only checks
~/.codex/auth.jsonfor presence (a single stat) and never reads, parses, copies, logs, or transmits it — thecodexCLI subprocess is the only reader. The connector points that subprocess at your~/.codex(viaHOME/CODEX_HOME) so a headless service can find the file, and strips its own Synx credentials from the subprocess environment.- ChatGPT login — run
A preflight runs before each Codex session launches: if neither a
ChatGPT login (~/.codex/auth.json) nor an API key
(OPENAI_API_KEY/CODEX_API_KEY) is present, the launch fails fast with a
clear, actionable error instead of letting the CLI fail opaquely mid-turn:
No Codex credential found: sign in with `codex login` (creates ~/.codex/auth.json)
or set OPENAI_API_KEY / CODEX_API_KEY in the daemon's environment.Permission modes under Codex
The connector's permission modes (the permissionCeiling in
~/.synx/config.json, and the mode a launch requests) apply to both agents.
Under Codex each mode maps to a Codex approvalPolicy × sandboxMode pair:
| Connector mode | Codex approvalPolicy | Codex sandboxMode | Network |
| ------------------- | ---------------------- | ------------------- | ------- |
| ask (default) | on-request | workspace-write | off |
| acceptEdits | on-failure | workspace-write | off |
| bypassPermissions | never | workspace-write | off |
In every mode:
- Network access is off — set explicitly, never left to a sandbox default.
- Writes are confined to the registered folder (the session's
cwd); there is no additional writable directory. Codex'sdanger-full-accesssandbox is never used from any mode. bypassPermissionsunder Codex is still sandbox-confined. Unlike Claude's bypass mode — which has no OS sandbox — Codex'sneverapproval policy still runs inside theworkspace-writesandbox, so it remains bounded to the registered folder with no network. It is therefore a safer bypass than the Claude path's.
The permissionCeiling is enforced identically for both agents upstream of
this mapping, so tightening it (e.g. to forbid bypassPermissions) has the same
effect no matter which agent is selected.
Commands
| Command | Description |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| login [--api-url <url>] | OAuth 2.1 + PKCE login; saves tokens to ~/.synx/credentials.json (chmod 0600). |
| logout | Best-effort server-side revoke + delete local credentials. |
| register <path> --workspace <slug\|id> [--name <name>] | Validate + consent + bind a folder into a workspace. |
| unregister <path> | Remove a folder (server + local registry). |
| list | List locally-registered folders. |
| status | Show login / device / folder / config summary. |
| run [--api-url <url>] | Run the foreground daemon. |
| service install\|uninstall\|status [--activate] [--deactivate] | Manage the background service unit (systemd / launchd / Windows Task Scheduler). --activate on install (and --deactivate on uninstall) also runs the activation commands instead of only printing them. |
Global flags: --log-level debug|info|warn|error (default info). --api-url
is accepted on every host-relevant command so one binary can target dev / prod /
local without re-login churn; the default is https://api.synx-ai.app.
login
synx-connector loginOpens your browser to the Synx consent page (OAuth 2.1 with PKCE; the connector
requests the connector scope). On the consent screen you confirm that this
machine may connect to your account on your behalf — that is what the consent
checkbox means: it authorizes the daemon to hold a token that lets workspace
members start agent sessions on the folders you later register, nothing more. It
does not grant access to any folder until you explicitly register one.
On success, tokens are written to ~/.synx/credentials.json (chmod 0600). The
client registration is idempotent per API URL: a second login reuses the
stored client_id.
register <path> --workspace <slug|id>
synx-connector register ~/code/my-project --workspace my-team --name "My Project"Registering a folder is the trust boundary. Before anything is sent to the server the CLI:
- validates the path is an existing absolute directory on this machine;
- resolves
--workspaceagainst your memberships; - prints a transcript-storage consent prompt and requires an explicit
y.
The consent language makes the stakes explicit: sessions in this folder
execute code on this machine, and the full transcript (the agent's
messages, the tools it runs, and their output) is sent to Synx and stored
there so it can be viewed and resumed from the web app. Do not register a folder
containing secrets you would not want captured in a stored transcript. Only on
your y is the folder bound server-side and recorded in the local registry.
run
synx-connector runRuns the daemon in the foreground: it opens the authenticated WebSocket to Synx
and waits for session launches. Useful for a first smoke test or under your own
supervisor. For an always-on setup, prefer service install.
service install | uninstall | status
synx-connector service installInstalls the daemon as a user-level background service. By default it writes
the unit/plist and prints the activation commands to run — it does not
shell out to systemctl/launchctl/schtasks, so you stay in control and it
works headless. Pass --activate to have it run those activation commands
for you (one-command setup); pass --deactivate on uninstall to run the
teardown commands:
Linux (systemd user unit) — written to
~/.config/systemd/user/synx-connector.service, then:systemctl --user daemon-reload systemctl --user enable --now synx-connector.service # (optional) keep it running after logout: loginctl enable-linger "$USER"The unit sets an explicit
PATH=in[Service]. This is deliberate: headless systemd provides a minimal environment, and the Agent SDK spawns a subprocess that needs a usablePATHto find its tools. The unit seedsPATHwith the directory of the current Node binary plus the common bins. No TTY is required — the daemon never prompts on this path; permission decisions are answered from the Synx web app.macOS (launchd LaunchAgent) — written to
~/Library/LaunchAgents/com.synx.connector.plist, then:launchctl load ~/Library/LaunchAgents/com.synx.connector.plistThe plist sets
RunAtLoad,KeepAlive, and an explicitPATHenvironment variable for the same headless reason.Windows (Task Scheduler logon task) — the recommended one-command path is to let
service install --activatecreate AND start the task for you (see the Windows setup section below). The write-only default still applies for headless installs:service installwrites the task XML to$env:USERPROFILE\.synx\synx-connector-task.xml(PowerShell) plus a small hidden-window launch shim, then prints the activation commands so you can run them yourself:schtasks /Create /TN SynxConnector /XML "$env:USERPROFILE\.synx\synx-connector-task.xml" /F schtasks /Run /TN SynxConnectorThe task is a per-user logon task named
SynxConnector: it starts the daemon at user logon and is configured to restart on failure. It runs in the context of the interactive user, so it inherits your PATH — which is whynodeand theclaudeCLI must be resolvable on that user's PATH (see Prerequisites for Windows below). The daemon runs with a hidden console window (launched via awscript.exeVBScript shim) so nothing pops up at logon, while keepingInteractiveTokenso the Agent SDK'sclaudesubprocess still inherits your interactive PATH. Check it with:schtasks /Query /TN SynxConnector
service status reports whether the unit/plist/task exists; service uninstall
removes it (and the launch shim) and prints the disable/unload commands (on
Windows, schtasks /Delete /TN SynxConnector /F), or pass --deactivate to run
them for you. Pass --api-url <url> to bake a non-default API URL into the unit.
Windows quoting/shell gotchas. Use PowerShell and its native
$env:USERPROFILE— cmd's%USERPROFILE%does not expand in PowerShell. Use plain straight double quotes ("), never smart/curly quotes. Do not runschtasksfrom git-bash/MSYS: it mangles/-prefixed args (e.g./Createis rewritten to a path). Run it from PowerShell orcmd. Ifschtasksreports access-denied, run it from an elevated shell — and note the ownership gotcha: a task created from an elevated shell can only be managed (run/delete) from an elevated shell.
Windows setup (PowerShell)
The connector binary is cross-platform; on Windows the background service is a Task Scheduler logon task rather than a systemd/launchd unit. The full path:
Prerequisites. Node.js ≥ 20, and the
claudeCLI installed and on your PATH. The Agent SDK spawns theclaudeCLI as a subprocess; on Windows the connector runs as a logon task that inherits the interactive user's PATH, soclaudemust be resolvable on that user's PATH for sessions to launch.Install the connector globally and confirm the version:
npm i -g synx-connector synx-connector --versionLog in (opens your browser for the OAuth consent screen):
synx-connector loginRegister a folder into a workspace, then list your registered folders:
synx-connector register C:\path\to\project --workspace <slug> synx-connector listInstall the service (recommended one-command path).
service install --activatewrites the task XML to$env:USERPROFILE\.synx\synx-connector-task.xml(plus a hidden-window launch shim) and creates + starts the scheduled task for you:synx-connector service install --activateIf
schtasksreports access-denied, re-run this from an elevated (Run as administrator) PowerShell. Remember the ownership gotcha: a task created elevated can only be managed elevated.Headless / manual alternative. Plain
service install(no--activate) only writes the files and prints the activation commands — run them yourself. Use PowerShell's$env:USERPROFILE(not cmd's%USERPROFILE%), plain straight quotes, and do not run these from git-bash (MSYS mangles the/-args):synx-connector service install # then run the printed commands: schtasks /Create /TN SynxConnector /XML "$env:USERPROFILE\.synx\synx-connector-task.xml" /F schtasks /Run /TN SynxConnectorConfirm it took:
synx-connector service status schtasks /Query /TN SynxConnectorUninstall when you're done.
service uninstall --deactivatedeletes the task and removes the files for you:synx-connector service uninstall --deactivateOr run the printed delete command yourself after a plain
service uninstall(a task created elevated must be deleted from an elevated shell):synx-connector service uninstall # then run the printed command: schtasks /Delete /TN SynxConnector /F
On Windows, the OAuth credentials at %USERPROFILE%\.synx\credentials.json are
restricted to the current user via an icacls owner-only ACL (on POSIX the same
protection is chmod 0600).
Local state (~/.synx/)
| File | Contents | Perms |
| ------------------ | -------------------------------------------------- | ------ |
| credentials.json | OAuth tokens | 0600 |
| device.json | this install's registered device id | 0600 |
| folders.json | the local folder registry (the trust boundary) | 0600 |
| config.json | optional config (see below) | — |
The ~/.synx directory itself is 0700.
Configuration (~/.synx/config.json)
All fields optional:
{
"permissionCeiling": ["ask", "acceptEdits"],
"maxConcurrentSessions": 4
}permissionCeiling— the permission modes this machine will accept on a server-launched session. Default: all modes (ask,acceptEdits,bypassPermissions). Tighten it to e.g. forbidbypassPermissionson a shared box; a launch above the ceiling is rejected locally. With the defaultask, the agent pauses for your approval on each sensitive tool use (you answer from the Synx web app).maxConcurrentSessions— concurrency cap. Default4(matches the server).sessionIdleTimeoutMinutes— idle-reap window: a multi-turn session sitting inawaiting_inputwith no new input for this long is ended by the daemon. Default30; range1–1440.sessionMaxLifetimeMinutes— max wall-clock session lifetime: every session ends this long after launch regardless of activity (the idle reaper only bounds abandoned sessions, since input resets it). The end is graceful and identical to an idle reap — pending prompts settle as deny and the session shows as completed — with the distinct reason "max session lifetime exceeded". Default720(12 h); bounded30–1440(30 min – 24 h): it is a security control, so it cannot be disabled or made effectively infinite. An out-of-range value fails config validation and the defaults apply.
Security model — what Synx can and cannot do to your machine
Can:
- Run an AI coding-agent session only in a folder you explicitly
registered — and only at the exact(folderId, path)recorded in your local registry. A compromised or malicious server cannot make the daemon execute code in a folder you never registered, nor swap the path under a known folder. - Stream the transcript of those sessions back to Synx, where it is stored (see the registration consent) and retained for 30 days.
Cannot:
- Touch any folder outside your local registry.
- Escalate past your
permissionCeiling. With the defaultaskmode, permission prompts are on by default — each sensitive tool use waits for your explicit approval, and a pending request defaults to deny after about 10 minutes. - See your OAuth token in any log: the bearer is sent only in the
Authorizationheader on the WebSocket upgrade — never as a query-string token — and is scrubbed from every log field.
How to revoke:
- Web app —
Settings → Connected apps: revoke the connector's OAuth grant. - Machines page: revoke an individual device (kill switch). The daemon's socket is closed immediately and it can launch nothing further.
synx-connector logout: deletes local credentials and attempts a best-effort server-side revocation.
MCP servers in headless sessions
Sessions launched through the connector run the coding agent headless on
your machine, and the agent loads MCP server configuration exactly like an
interactive run: the registered folder's .mcp.json and the machine user's
user-scope MCP config both apply. Two consequences:
- OAuth-requiring MCP servers won't work. A server that needs an
interactive browser sign-in (for example a hosted HTTP server like
mcp.supabase.com) can never complete that flow in a headless session. The daemon surfaces each failed/auth-requiring server as anerrorentry in the session transcript ("MCP server 'x' failed to connect … continuing without it") and the run continues without it. - Connection attempts are time-bounded. The daemon sets an MCP startup
timeout of 30 s (via the agent's
MCP_TIMEOUTenvironment variable) so an unreachable or auth-blocked server cannot silently stall the turn for minutes. SetMCP_TIMEOUTyourself in the daemon's environment to override.
If a folder's sessions don't need MCP, remove unused servers from that folder's
.mcp.json — and remember user-scope servers apply to every folder on the
machine.
Troubleshooting
- It says my device is offline / a launch failed with a conflict. The daemon
isn't connected. Make sure
synx-connector run(or the installed service) is running on the machine, and checksynx-connector status. A launch can also conflict if the device is already at its concurrent-session cap. - The connection keeps closing with code
4401(auth). Your credentials are bad, missing, or expired. Runsynx-connector loginagain to re-authenticate. - The connection closed with code
4403(revoked). This device's grant was revoked (from the web app or another machine) while it was connected — reconnecting won't help. Re-register by runningsynx-connector loginagain, or check whether the revocation was intentional. - The connection closed with code
4408(protocol violation). A malformed message was detected on the channel. This usually means a version mismatch — updatesynx-connector(npm i -g synx-connector@latest). - A session won't launch and Synx says "upgrade required". The connector is a paid-tier feature; the workspace owner's plan must include it.
- A launch is rejected locally as "above the permission ceiling". The
requested permission mode exceeds your
permissionCeilingin~/.synx/config.json. Widen the ceiling (or launch with a lower mode).
Logs
Structured JSON, one object per line (stdout for info/debug, stderr for
warn/error) so a systemd/launchd journal can parse them. Tokens are scrubbed
from every field defensively. Use --log-level debug for verbose output.
License
MIT — see LICENSE.
