How to Set Up Claude Code Remote-Control as a Launchd Service on Mac
I wanted to access Claude Code from my phone. The remote-control feature seemed perfect, but I didn’t want to manually start it every time my Mac rebooted or if it crashed. I needed a permanent background service that would start automatically and stay running.
The Problem with Manual Startup
I initially tried running claude remote-control manually each time I needed it. This quickly became tedious:
- Open Terminal
- Navigate to my project directory
- Run
claude remote-control - Keep the terminal window open
- Repeat whenever my Mac restarted or the process crashed
I tried using a Telegram bot as a bridge initially, but that added unnecessary complexity and dependencies. I wanted something native to macOS that would just work.
Finding the Right Approach
After some research, I discovered that claude remote-control should be run as a standalone command, not inside a Claude Code session. This was my first mistake - I had been trying to run it from within Claude Code itself.
The key insight was that macOS has a built-in service manager called launchd that can manage long-running processes. This is exactly what I needed.
Step 1: Enable Remote Control in Claude Code
First, I needed to enable the remote-control feature in Claude Code:
claude /configThis opens the configuration menu where I enabled the remote-control option. I made note of the authentication token it provided - I’d need this later to connect from my phone.
Step 2: Find the Claude Binary Path
I needed the full path to the claude binary for the launchd plist file:
which claudeOutput:
/opt/homebrew/bin/claudeYour path might be different depending on how you installed Claude Code. If you used npm or yarn, it might be in a different location. I’m using Homebrew on an Apple Silicon Mac, so mine is in /opt/homebrew/bin/.
Step 3: Create the LaunchAgent Plist File
I created a new plist file in the LaunchAgents directory:
touch ~/Library/LaunchAgents/com.claude.remote-control.plistLaunchAgents run as the current user and start when you log in, which is exactly what I needed. LaunchDaemons run as root and start at boot, but that would be overkill for this use case.
Then I opened it in my editor and added the configuration:
<?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>com.claude.remote-control</string>
<key>ProgramArguments</key> <array> <string>/opt/homebrew/bin/claude</string> <string>remote-control</string> </array>
<key>WorkingDirectory</key> <string>/Users/cowrie/projects</string>
<key>RunAtLoad</key> <true/>
<key>KeepAlive</key> <true/>
<key>StandardOutPath</key> <string>/tmp/claude-remote-control.log</string>
<key>StandardErrorPath</key> <string>/tmp/claude-remote-control.error.log</string>
<key>EnvironmentVariables</key> <dict> <key>PATH</key> <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string> </dict></dict></plist>Let me explain each key:
- Label: A unique identifier for the service. I used a reverse-domain naming convention.
- ProgramArguments: The command to run. The first string is the full path to the binary, followed by arguments.
- WorkingDirectory: This is crucial - it sets the directory where Claude Code will operate. I set it to my main projects folder.
- RunAtLoad: Start the service immediately when loaded.
- KeepAlive: Automatically restart the service if it crashes. This was the key feature I needed.
- StandardOutPath/StandardErrorPath: Log files for debugging. I put them in
/tmpso they’re cleared on reboot but accessible for troubleshooting. - EnvironmentVariables: I included the PATH to ensure Claude can find its dependencies.
Step 4: Load and Start the Service
I loaded the service using launchctl:
launchctl load ~/Library/LaunchAgents/com.claude.remote-control.plistThis should start the service immediately because of the RunAtLoad key. But I wanted to verify it was actually running.
Step 5: Verify the Service is Running
I checked the service status:
launchctl list | grep claudeOutput:
12345 0 com.claude.remote-controlThe first number is the process ID (PID), and the second is the exit code. An exit code of 0 means the service is running successfully.
I also checked the log files:
tail -f /tmp/claude-remote-control.logThis showed me the remote-control startup output, including the URL I could use to connect from my phone.
Troubleshooting Common Issues
When I first set this up, I made several mistakes that prevented the service from starting correctly.
Incorrect Binary Path
My first attempt failed because I used the wrong path to the Claude binary:
tail -20 /tmp/claude-remote-control.error.logThe error log showed:
launchd: Could not find program: /usr/local/bin/claudeI had assumed the binary was in /usr/local/bin but it was actually in /opt/homebrew/bin. Running which claude again and updating the plist fixed this.
Missing Working Directory
I initially forgot to set the WorkingDirectory. The service started but Claude Code couldn’t find my projects. Adding the WorkingDirectory key with my projects folder path resolved this.
Service Not Auto-Restarting
At one point, the service kept crashing immediately. I checked the error log:
cat /tmp/claude-remote-control.error.logThe issue was a missing configuration file. Once I fixed that, the KeepAlive setting worked correctly and the service restarted automatically.
Reloading After Plist Changes
When I needed to modify the plist file, I had to unload and reload the service:
launchctl unload ~/Library/LaunchAgents/com.claude.remote-control.plistlaunchctl load ~/Library/LaunchAgents/com.claude.remote-control.plistSimply editing the file doesn’t apply changes - you must reload the service.
Why This Approach Works Better
This launchd approach gave me several advantages over manual startup:
Automatic Startup: The service starts when I log in, so it’s always ready. I don’t need to remember to start it manually.
Auto-Restart: If the remote-control process crashes, launchd automatically restarts it. This keeps the service available even when unexpected errors occur.
No External Dependencies: I don’t need Telegram bots or third-party services. Everything runs locally on my Mac using built-in macOS tools.
Persistent Access: I can now reliably connect to Claude Code from my phone anytime my Mac is on. The service stays running in the background, ready for connections.
Easy Management: Using standard launchctl commands, I can start, stop, reload, and check status of the service. It’s well-documented and familiar to anyone who’s worked with macOS services.
Managing the Service
Here are the commands I use to manage the service day-to-day:
launchctl stop com.claude.remote-controllaunchctl start com.claude.remote-controllaunchctl unload ~/Library/LaunchAgents/com.claude.remote-control.plistlaunchctl unload ~/Library/LaunchAgents/com.claude.remote-control.plist && \launchctl load ~/Library/LaunchAgents/com.claude.remote-control.plistConnecting from My Phone
With the service running, I can now connect from my phone. The remote-control feature provides a URL that looks like:
https://claude.ai/remote/xxxxx-xxxxx-xxxxxI bookmarked this URL on my phone’s home screen. When I open it, I’m connected to my Mac’s Claude Code session and can interact with it just like I’m at my desk.
Summary
In this post, I showed how to set up Claude Code’s remote-control as a launchd service on Mac. The key point is to run claude remote-control as a standalone command, use the correct binary path, set a valid working directory, and configure auto-restart with KeepAlive. This gives you permanent, reliable access to Claude Code from any device without manual intervention.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments