| name | claude-code-service |
| description | Run Claude Code as a background service, web server, or API endpoint instead of CLI. Enable always-on Claude assistance, web UI access, API integrations, scheduled tasks, and automated workflows. Transform Claude Code from command-line tool to persistent service infrastructure. |
Claude Code as a Service
Transform Claude Code from a command-line tool into a persistent background service with web UI, API access, and automated workflows.
Service Modes
1. Background Service (Daemon)
- Runs continuously in background
- Responds to system events
- Handles scheduled tasks
- Always-available Claude assistance
2. Web Server
- Browser-based UI
- Chat interface accessible at http://localhost:8080
- File upload/download
- Multi-session support
3. API Endpoint
- RESTful HTTP API
- Integration with other tools
- Programmatic access
- Webhook support
4. IDE Integration
- VS Code extension
- JetBrains plugin
- Sublime Text integration
- Direct editor access
Quick Start
Web Server Mode
Start web server:
claude-code serve --port 8080
claude-code serve --port 8080 --auth
claude-code serve --port 8443 --ssl-cert cert.pem --ssl-key key.pem
Access: Open browser to http://localhost:8080
Background Service Mode
Start as daemon:
claude-code daemon start
claude-code daemon status
claude-code daemon stop
Installation as System Service
Windows Service
Install:
nssm install ClaudeCode "C:\Python\python.exe" "C:\path\to\claude-code\serve.py"
nssm set ClaudeCode AppDirectory "C:\path\to\claude-code"
nssm set ClaudeCode DisplayName "Claude Code Service"
nssm set ClaudeCode Description "Claude AI Assistant Service"
nssm start ClaudeCode
Or use PowerShell:
# Create service
New-Service -Name "ClaudeCode" `
-BinaryPathName "C:\Python\python.exe C:\path\to\claude-code\serve.py" `
-DisplayName "Claude Code Service" `
-Description "Claude AI Assistant Service" `
-StartupType Automatic
# Start service
Start-Service ClaudeCode
Script-based installer:
python scripts/install_windows_service.py
macOS LaunchAgent
Create LaunchAgent:
python scripts/install_macos_service.py
cat > ~/Library/LaunchAgents/com.anthropic.claudecode.plist << 'EOF'
<?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.anthropic.claudecode</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/python3</string>
<string>/path/to/claude-code/serve.py</string>
<string>--port</string>
<string>8080</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/claudecode.log</string>
<key>StandardErrorPath</key>
<string>/tmp/claudecode.error.log</string>
</dict>
</plist>
EOF
launchctl load ~/Library/LaunchAgents/com.anthropic.claudecode.plist
launchctl list | grep claudecode
Start/Stop:
launchctl start com.anthropic.claudecode
launchctl stop com.anthropic.claudecode
launchctl unload ~/Library/LaunchAgents/com.anthropic.claudecode.plist
Linux Systemd Service
Create systemd service:
python scripts/install_linux_service.py
cat > ~/.config/systemd/user/claude-code.service << 'EOF'
[Unit]
Description=Claude Code Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /path/to/claude-code/serve.py --port 8080
Restart=always
RestartSec=10
Environment="PATH=/usr/bin:/usr/local/bin"
WorkingDirectory=/path/to/claude-code
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable claude-code
systemctl --user start claude-code
systemctl --user status claude-code
Manage service:
systemctl --user start claude-code
systemctl --user stop claude-code
systemctl --user restart claude-code
journalctl --user -u claude-code -f
systemctl --user disable claude-code
Web Server Implementation
Basic Flask Server
serve.py:
"""
Claude Code Web Server
"""
from flask import Flask, render_template, request, jsonify, session
from flask_cors import CORS
import os
import sys
app = Flask(__name__)
app.secret_key = os.urandom(24)
CORS(app)
from claude_code import ClaudeCode
claude = ClaudeCode()
@app.route('/')
def index():
"""Main chat interface"""
return render_template('index.html')
@app.route('/api/chat', methods=['POST'])
def chat():
"""Chat endpoint"""
data = request.json
message = data.get('message', '')
response = claude.chat(message)
return jsonify({
'response': response,
'status': 'success'
})
@app.route('/api/file/upload', methods=['POST'])
def upload_file():
"""File upload endpoint"""
if 'file' not in request.files:
return jsonify({'error': 'No file provided'}),
file = request.files[]
result = claude.process_file(file)
jsonify(result)
():
jsonify({
: ,
:
})
__name__ == :
port = (os.environ.get(, ))
app.run(host=, port=port, debug=)
Web UI (HTML/JavaScript)
templates/index.html:
<!DOCTYPE html>
<html>
<head>
<title>Claude Code</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
margin: 0;
padding: 0;
background: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.chat-box {
background: white;
border-radius: 8px;
padding: 20px;
min-height: 400px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.message {
margin-bottom: 16px;
padding: ;
: ;
}
{
: ;
: white;
: ;
}
{
: ;
: ;
}
{
: flex;
: ;
}
{
: ;
: ;
: solid ;
: ;
: ;
}
{
: ;
: ;
: white;
: none;
: ;
: pointer;
: ;
}
{
: ;
}
Claude Code
Send
API Endpoint Mode
RESTful API
API server with full REST endpoints:
"""
Claude Code REST API
"""
from fastapi import FastAPI, File, UploadFile, HTTPException
from pydantic import BaseModel
import uvicorn
app = FastAPI(title="Claude Code API", version="1.0.0")
class ChatRequest(BaseModel):
message: str
context: str = None
class ChatResponse(BaseModel):
response: str
tokens_used: int
@app.post("/api/v1/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
"""Chat with Claude"""
response = claude.chat(request.message, context=request.context)
return ChatResponse(
response=response,
tokens_used=len(response) // 4
)
@app.post("/api/v1/file/analyze")
async def analyze_file(file: UploadFile = File(...)):
"""Analyze uploaded file"""
contents = await file.read()
analysis = claude.analyze_file(contents, file.filename)
return {"analysis": analysis}
@app.post()
():
review = claude.review_code(code, language)
{: review}
():
{: , : }
__name__ == :
uvicorn.run(app, host=, port=)
API Usage:
curl -X POST http://localhost:8080/api/v1/chat \
-H "Content-Type: application/json" \
-d '{"message": "Hello Claude"}'
curl -X POST http://localhost:8080/api/v1/file/analyze \
-F "file=@document.pdf"
curl -X POST http://localhost:8080/api/v1/code/review \
-H "Content-Type: application/json" \
-d '{"code": "def hello():\n print(\"hi\")", "language": "python"}'
Configuration
config.yaml:
server:
mode: "web"
host: "0.0.0.0"
port: 8080
ssl:
enabled: false
cert: "cert.pem"
key: "key.pem"
authentication:
enabled: true
type: "basic"
users:
- username: "admin"
password: "hashed_password"
claude:
api_key: "${ANTHROPIC_API_KEY}"
model: "claude-sonnet-4-20250514"
max_tokens: 4096
logging:
level: "INFO"
file: "/var/log/claude-code/service.log"
rotation: "daily"
max_size: "100MB"
storage:
sessions: "/var/lib/claude-code/sessions"
uploads: "/var/lib/claude-code/uploads"
security:
cors_origins: ["http://localhost:3000"]
max_upload_size: "10MB"
rate_limit:
Security
Authentication
Basic authentication:
from functools import wraps
from flask import request, Response
def check_auth(username, password):
"""Verify credentials"""
return username == 'admin' and password == 'secret'
def authenticate():
"""Send 401 response"""
return Response(
'Authentication required', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'}
)
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
@app.route('/api/chat', methods=['POST'])
@requires_auth
def chat():
pass
Token-based authentication:
from functools import wraps
from flask import request, jsonify
API_TOKENS = {
'token123': 'user1',
'token456': 'user2'
}
def require_token(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if token not in API_TOKENS:
return jsonify({'error': 'Invalid token'}), 401
return f(*args, **kwargs)
return decorated
@app.route('/api/chat', methods=['POST'])
@require_token
def chat():
pass
SSL/TLS
Run with HTTPS:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
python serve.py --ssl-cert cert.pem --ssl-key key.pem
In Flask:
if __name__ == '__main__':
app.run(
host='0.0.0.0',
port=8443,
ssl_context=('cert.pem', 'key.pem')
)
Rate Limiting
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["60 per minute"]
)
@app.route('/api/chat', methods=['POST'])
@limiter.limit("10 per minute")
def chat():
pass
Automated Workflows
Scheduled Tasks
Using APScheduler:
from apscheduler.schedulers.background import BackgroundScheduler
import atexit
scheduler = BackgroundScheduler()
@scheduler.scheduled_job('cron', hour=9)
def morning_summary():
"""Daily morning summary"""
summary = claude.generate_summary(period='yesterday')
send_email(summary)
@scheduler.scheduled_job('interval', minutes=30)
def check_emails():
"""Check for urgent emails"""
urgent = check_urgent_emails()
if urgent:
notify(urgent)
scheduler.start()
atexit.register(lambda: scheduler.shutdown())
Webhook Integration
Receive webhooks:
@app.route('/webhook/github', methods=['POST'])
def github_webhook():
"""Handle GitHub webhook"""
payload = request.json
if payload['action'] == 'opened':
pr = payload['pull_request']
review = claude.review_pr(pr)
post_github_comment(pr['number'], review)
return jsonify({'status': 'processed'})
@app.route('/webhook/slack', methods=['POST'])
def slack_webhook():
"""Handle Slack command"""
command = request.form['text']
response = claude.chat(command)
return jsonify({
'response_type': 'in_channel',
'text': response
})
Monitoring and Logging
Logging Setup
import logging
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
'claude-code.log',
maxBytes=10*1024*1024,
backupCount=5
)
handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s'
))
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
Health Monitoring
@app.route('/health')
def health():
"""Health check endpoint"""
checks = {
'database': check_database(),
'api': check_claude_api(),
'disk': check_disk_space(),
'memory': check_memory()
}
all_healthy = all(checks.values())
status_code = 200 if all_healthy else 503
return jsonify({
'status': 'healthy' if all_healthy else 'unhealthy',
'checks': checks
}), status_code
Metrics
Prometheus metrics:
from prometheus_client import Counter, Histogram, generate_latest
REQUEST_COUNT = Counter('requests_total', 'Total requests')
REQUEST_LATENCY = Histogram('request_latency_seconds', 'Request latency')
@app.before_request
def before_request():
request.start_time = time.time()
@app.after_request
def after_request(response):
REQUEST_COUNT.inc()
REQUEST_LATENCY.observe(time.time() - request.start_time)
return response
@app.route('/metrics')
def metrics():
return generate_latest()
Docker Deployment
Dockerfile:
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application
COPY . .
# Expose port
EXPOSE 8080
# Run service
CMD ["python", "serve.py", "--port", "8080"]
docker-compose.yml:
version: '3.8'
services:
claude-code:
build: .
ports:
- "8080:8080"
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
volumes:
- ./data:/app/data
restart: unless-stopped
Run:
docker-compose build
docker-compose up -d
docker-compose logs -f
docker-compose down
IDE Integration Examples
VS Code Extension
package.json:
{
"name": "claude-code-extension",
"version": "1.0.0",
"engines": {
"vscode": "^1.70.0"
},
"activationEvents": ["*"],
"main": "./extension.js",
"contributes": {
"commands": [
{
"command": "claudecode.askClaude",
"title": "Ask Claude"
}
]
}
}
Sublime Text Plugin
claude_code.py:
import sublime
import sublime_plugin
import requests
class AskClaudeCommand(sublime_plugin.TextCommand):
def run(self, edit):
selection = self.view.substr(self.view.sel()[0])
response = requests.post(
'http://localhost:8080/api/chat',
json={'message': selection}
)
result = response.json()['response']
self.view.insert(edit, self.view.sel()[0].end(), '\n\n' + result)
Scripts Reference
Installation:
install_windows_service.py - Windows service installer
install_macos_service.py - macOS LaunchAgent installer
install_linux_service.py - Linux systemd installer
Server:
serve.py - Web server
api_server.py - API endpoint server
daemon.py - Background daemon
Management:
start_service.py - Start service
stop_service.py - Stop service
status.py - Check status
configure.py - Interactive configuration
Best Practices
- Security first - Always use authentication for public-facing services
- Monitor resources - Track memory and CPU usage
- Implement logging - Debug issues effectively
- Rate limiting - Prevent abuse
- Error handling - Graceful failure recovery
- Auto-restart - Use service managers for reliability
- Backup configuration - Save settings regularly
- Update regularly - Keep dependencies current
Troubleshooting
Service won't start
which python3
python3 serve.py --port 8080
Port already in use
python serve.py --port 8081
Permission denied
# Linux/macOS: May need sudo for ports < 1024
# Or use port > 1024 (recommended)
# Windows: Run as Administrator
Reference Documentation