Skip to main content

remi [args]

Start Claude Code with transparent remote monitoring.

Wrapper Mode (Default)

remi [options] [claude-args...]

Remi spawns Claude Code in the foreground and passes your terminal through directly. You interact with Claude as usual. In the background, Remi:

  1. Starts a WebSocket server for remote clients (default port 18765)
  2. Starts a hook HTTP server on an OS-assigned port and writes hook configuration to .claude/settings.local.json
  3. Connects to the signaling relay for code-based access
  4. Receives structured events from Claude Code via hooks (tool use, permission prompts, status changes)
  5. Writes status to ~/.remi/status-<port>.json for the Claude Code status line

All wrapper-mode logs go to ~/.remi/remi.log so they never interfere with your terminal.

Examples

# Start in current directory
remi

# Pass arguments to Claude Code
remi my-project

# Resume a previous session
remi --resume

# Custom port
remi --port 9000 my-project

# Disable relay (local-only)
remi --no-relay my-project

# Localhost-only (no network exposure, no mDNS)
remi --local my-project

The --local flag is a shorthand for --bind localhost --no-mdns. It binds only to localhost (no network exposure) and disables mDNS advertising. Useful when you only need local browser access or SSH tunnel connections.

Daemon Mode

remi --daemon

Runs Remi as a headless server without a local terminal. Claude sessions are created on demand when a remote client connects. Output goes to the console instead of the log file.

Daemon mode is used with --install for running Remi as a persistent service.

Options

Server options

OptionDescriptionDefaultEnv Var
--port PORTWebSocket server port18765REMI_PORT
--bind HOSTBind WebSocket server to HOST0.0.0.0 (all interfaces)
--daemonHeadless daemon modeoff
--localLocalhost-only mode (same as --bind localhost --no-mdns)off
--orphan-timeout SECSHow long orphaned sessions stay alive when no client is connected. Set to 0 to disable cleanup.300 (5 minutes)
--max-bullet-length NTruncate message bullets longer than N characters (0 to disable)500REMI_MAX_BULLET_LENGTH

Relay and network options

OptionDescriptionDefault
--no-relayDisable signaling relay (no remote code access)off
--no-mdnsDisable mDNS network advertisingoff
--no-telegramDisable Telegram adapteroff
--signaling-url URLCustom signaling server URLwss://remi-signaling.yooz.workers.dev/connect
--permanent-codeUse a persistent connection code (requires Ed25519 auth for relay connections)off
--networkDiscover sessions across the local network (for ls)off

Authentication options

OptionDescriptionDefault
--authForce enable authenticationauto (enabled for 0.0.0.0)
--no-authDisable authenticationoff
--no-tofuReject unknown clients (disable Trust On First Use)off

Subcommand-specific options

See also Global Options for --host, --dir, and --recent.

OptionDescription
--forceOverwrite existing identity (for keygen, import-key)
--passphraseEncrypt identity with a passphrase (for keygen)
--decryptRemove passphrase from existing identity (for keygen)
--encryptAdd passphrase to existing identity (for keygen)
--label NAMELabel for authorized key (for authorize)
--public-onlyExport only public key (for export-key)
--remove FINGERPRINTRemove authorized key by fingerprint (for authorize)
note

In wrapper and daemon mode, when authentication is enabled (the default for network binds), Remi needs to unlock your identity at startup. If your identity was created with --passphrase, you will be prompted for the passphrase (or set REMI_PASSPHRASE). Unencrypted identities (the default) unlock automatically. Subcommands like ls, attach, recent, and kill do not require identity unlocking.

How It Works

In wrapper mode, Remi uses Bun's native PTY support to spawn Claude Code. Raw terminal bytes pass through to your terminal unchanged, preserving colors, cursor movement, and all ANSI escape sequences.

Hook-Based Event Detection

Before spawning Claude Code, Remi writes hook configuration into the project's .claude/settings.local.json. This configures Claude Code to POST structured JSON events to Remi's local hook server (http://127.0.0.1:<auto-port>/hooks).

Remi registers handlers for the full set of Claude Code hook events. The ones that change user-visible state:

Hook EventWhat Remi Does
SessionStartCaptures Claude's session ID and transcript path; locks on first match, detects restart vs foreign via PTY liveness
SessionEndResets tracker state so a subsequent SessionStart cleanly takes over
PreToolUseSets status to "executing" with the tool name; tracks Task/Agent nesting for subagent filtering
PostToolUseSets status to "thinking"; decrements subagent nesting depth
Notification (permission_prompt)Creates a Yes/No question and sets status to "waiting" (suppressed when a prior PermissionRequest already emitted one)
Notification (idle_prompt)Sets status to "idle"
PermissionRequestAuthoritative permission prompt with tool context and suggestions; feeds auto-approve when enabled
StopSets status to "idle" (when the agent turn actually ended) and clears stale subagent tracking
StopFailureEmits a retry question to the user
SubagentStart / SubagentStopStatus context for synchronous subagent runs
PostToolUseFailureSurfaces tool failures in status

Accepted but currently just forwarded to dynamic listeners (no default status change): UserPromptSubmit, InstructionsLoaded, TaskCompleted, TeammateIdle, ConfigChange, WorktreeCreate, WorktreeRemove, PreCompact, PostCompact, Elicitation, ElicitationResult. All 22 registered Claude Code hook events reach the server with a 200 response so Claude Code never gets blocked.

Subagent and team-member filtering

Claude Code tags hook events that originate from a background subagent or a team member with an agent_id field. Remi drops those events at the hook layer so subagent permission prompts never become phone notifications, never trigger auto-approve, and never inject keystrokes into the main PTY. See Auto-Approve guide — Multi-session behavior.

Session lock and restart detection

Remi treats its own PTY process as the ground truth for "main interactive session." When a hook event arrives with a different session_id than the current lock:

  • PTY still running + no explicit SessionEnd → event is foreign (subagent or sibling daemon), dropped.
  • PTY exited OR SessionEnd received → event is a genuine Claude restart, lock transitions to the new session.

/clear, /compact, and /resume emit SessionStart with a known source value; Remi pre-sets the ended flag so those transitions are recognized as lifecycle events rather than foreign events.

This replaces the earlier approach of parsing terminal output. Hooks provide structured, reliable event data directly from Claude Code, with no risk of ANSI escape sequence interference.

On shutdown, Remi removes its hook entries from .claude/settings.local.json, leaving any other settings intact.

Debug capture

Set REMI_HOOK_DEBUG=1 before starting Remi to dump every raw hook POST to ~/.remi/hook-diag.jsonl. Each line is a JSON object with a _ts timestamp and the raw payload. Useful for investigating hook routing issues.

Port Conflicts

In wrapper mode, if the WebSocket port is already in use, Remi logs a warning to ~/.remi/remi.log and continues. Claude Code works normally; only remote monitoring is disabled.

In daemon mode, a port conflict causes Remi to exit with an error.