Running the CLI long-term
Patterns for running convoy connect as a durable worker — dev container first, process supervisors as a fallback.
convoy connect is a long-running process. It registers a CLI
session, heartbeats, and claims thread work from Convex for as long as
it stays up. To keep threads executing for a team, it needs to be
running somewhere durable — not someone's laptop terminal.
Current status. Convoy has a working dev-container setup and a verified SIGINT/SIGTERM shutdown path, but there is no canonical production orchestration pattern yet. The guidance below leads with the dev container (which is the tested path) and documents process-supervisor options as secondary, not endorsed. Treat service examples as starting points to adapt, not recipes.
What the process actually needs
Any host running convoy connect needs:
- Node.js 18+.
- The runtime binary on
PATH— today that is Claude Code'sclaude. - A readable Convoy profile at
~/.config/convoy/config.toml(orCONVOY_PROFILEset), with a valid API key. - Outbound HTTPS to the Convex deployment and to the runtime provider (Anthropic or Bedrock).
- Optional: write access to
~/.cache/convoy/thread-attachments(thread attachment cache; 7-day GC).
Relevant flags (see convoy connect --help):
| Flag | Default | Purpose |
|---|---|---|
--profile <key> | resolved from cwd / env / default | Which saved profile to use |
--name <name> | current folder name | Display name for this session |
-c, --concurrency <n> | profile value, otherwise 5 (max 50) | How many claimed chats this session processes in parallel |
-s, --stream <path> | — | Replay a JSONL fixture instead of running a real runtime (useful for UI dev) |
Useful env vars:
CONVOY_PROFILE=<key>— select the profile non-interactivelyCONVOY_CLI_DEBUG=1— verbose logging, truncatedCONVOY_CLI_DEBUG=2— verbose logging, no truncation
Recommended: dev container
The repo ships with a pre-configured dev container under
.devcontainer/ that already:
- has
claudepreinstalled and pinned to Bedrock - mounts
~/.config/convoy/from the host so profile config survives container rebuilds - has an outbound firewall allowlist in
strictmode that already covers Convex, Anthropic, and Bedrock
See Dev Container setup
for the full walkthrough. Inside the container, run:
CONVOY_CLI_DEBUG=1 pnpm cli:dev connectIf you want this to stay running across VS Code window closures, keep
the container attached or run the command inside a tmux / screen
session inside the container.
The dev container is the currently-tested path. Anything beyond this is extrapolation.
Fallback: process supervisors
If you need the CLI running on a bare-metal machine, any standard
process supervisor will work. These examples assume the CLI has been
installed as convoy (see CLI install for the
distribution-status caveat until @useconvoy/cli ships).
systemd (Linux)
/etc/systemd/system/convoy-connect.service:
[Unit]
Description=Convoy CLI connect (<profile-key>)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=convoy
WorkingDirectory=/home/convoy/workspace
ExecStart=/usr/bin/env convoy connect --profile <profile-key>
Restart=on-failure
RestartSec=5
# Make sure the runtime binary is discoverable.
Environment=PATH=/usr/local/bin:/usr/bin:/bin
# Optional:
# Environment=CONVOY_CLI_DEBUG=1
# EnvironmentFile=/etc/convoy/env # for secrets not in the profile
StandardOutput=journal
StandardError=journal
SyslogIdentifier=convoy-connect
# Clean shutdown: SIGTERM triggers convoy's disconnect path.
KillSignal=SIGTERM
TimeoutStopSec=25
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now convoy-connect
sudo journalctl -u convoy-connect -flaunchd (macOS)
~/Library/LaunchAgents/dev.convoy.connect.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>dev.convoy.connect</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/convoy</string>
<string>connect</string>
<string>--profile</string>
<string><profile-key></string>
</array>
<key>WorkingDirectory</key><string>/Users/you/workspace</string>
<key>KeepAlive</key><true/>
<key>RunAtLoad</key><true/>
<key>StandardOutPath</key><string>/tmp/convoy-connect.log</string>
<key>StandardErrorPath</key><string>/tmp/convoy-connect.err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key><string>/usr/local/bin:/usr/bin:/bin</string>
</dict>
</dict>
</plist>launchctl load ~/Library/LaunchAgents/dev.convoy.connect.plist
launchctl list | grep convoyPM2 (quick-start on any host)
pm2 start convoy --name convoy-connect -- connect --profile <profile-key>
pm2 save
pm2 startup # follow printed instructions for boot persistence
pm2 logs convoy-connectDocker
A minimal container that runs convoy connect:
FROM node:20-bookworm-slim
# Claude Code must be installed inside the image — see Anthropic's docs
# for the current recommended install.
RUN npm install -g @anthropic-ai/claude-code
RUN npm install -g @useconvoy/cli
WORKDIR /workspace
CMD ["convoy", "connect"]Run it with the host profile directory mounted and outbound network available:
docker run -d --name convoy-connect \
-v $HOME/.config/convoy:/root/.config/convoy \
-v /path/to/workspace:/workspace \
convoy-connectWhat to verify after the service starts
- The session shows up as online in the web app's profile selector.
convoy statuson the same host resolves to the expected profile.- Sending a test thread from the web UI triggers streamed output.
- Stopping the service cleanly (
systemctl stop/launchctl unload/docker stop) results in the session disappearing from the profile selector within a few heartbeat intervals rather than lingering as "online but not processing".
Known open questions
- Which orchestration pattern will become canonical. Dev container is the most tested path; nothing above it has been through production use at scale.
- Multiple workers per profile. A profile has one active session at
a time (enforced server-side). Today the horizontal-scaling path is
multiple profiles, each on its own host. See
Scaling and limits. - Credential rotation without downtime. Rotating the API key in the profile TOML currently requires restarting the service.
If you deploy one of the fallback patterns above, notes on what worked and what didn't are valuable — open an issue or start a thread.