Headless Mode
Expose Autohand Code with Cloudflare Tunnel
Run Autohand Code on your VPS or Docker host, then expose SSH or an HTTP job trigger through an outbound Cloudflare Tunnel.
Provider docs used
Cloudflare Tunnel lets a connector make outbound-only connections from your server to Cloudflare. This tutorial follows Cloudflare's current Tunnel setup flow and adapts it for an Autohand Code runner: Create a tunnel.
Architecture
Autohand Code still runs on a Linux host or Docker host. Cloudflare Tunnel only handles remote access. The safest default is a local runner process bound to 127.0.0.1, with Cloudflare Access or a signed webhook in front of the public hostname.
| Expose | Use when |
|---|---|
| HTTP webhook | GitHub, Linear, Slack, or a Worker needs to trigger a job |
| SSH | Humans need private terminal access through Cloudflare Access |
| Local dashboard | You build a small internal UI for queued Autohand Code jobs |
Step 1: Run a local Autohand Code job trigger
Keep the process bound to localhost. The tunnel will publish it later.
// server.mjs
import { createServer } from "node:http";
import { spawn } from "node:child_process";
const repoDir = process.env.AUTOHAND_REPO_DIR;
const token = process.env.AUTOHAND_RUNNER_TOKEN;
createServer((req, res) => {
if (req.method !== "POST" || req.headers.authorization !== `Bearer ${token}`) {
res.writeHead(401);
res.end("unauthorized");
return;
}
const job = spawn("autohand", [
"-p",
"Review the latest repository state and summarize action items",
"--restricted",
"--output-format",
"stream-json"
], { cwd: repoDir, env: process.env });
job.stdout.on("data", chunk => process.stdout.write(chunk));
job.stderr.on("data", chunk => process.stderr.write(chunk));
res.writeHead(202);
res.end("queued\n");
}).listen(8787, "127.0.0.1");
Step 2: Create the tunnel
You can create the tunnel in the Cloudflare dashboard or with cloudflared. The dashboard flow gives you an install command for your connector host.
cloudflared tunnel login
cloudflared tunnel create autohand-runner
cloudflared tunnel route dns autohand-runner autohand-runner.example.com
Step 3: Route the hostname to localhost
# ~/.cloudflared/config.yml
tunnel: autohand-runner
credentials-file: /home/autohand/.cloudflared/autohand-runner.json
ingress:
- hostname: autohand-runner.example.com
service: http://127.0.0.1:8787
- service: http_status:404
cloudflared tunnel run autohand-runner
Step 4: Protect the route
For human access, put Cloudflare Access in front of the hostname. For machine access, validate a bearer token or webhook signature in the local runner before starting Autohand Code.
export AUTOHAND_RUNNER_TOKEN="$(openssl rand -hex 32)"
export AUTOHAND_REPO_DIR="$HOME/work/your-repo"
source ~/.autohand/env
node server.mjs
Step 5: Trigger a job
curl -X POST https://autohand-runner.example.com \
-H "Authorization: Bearer $AUTOHAND_RUNNER_TOKEN"
Operations checklist
- Keep the origin listener bound to
127.0.0.1. - Use Cloudflare Access for human-triggered jobs.
- Use request signing or bearer tokens for machine-triggered jobs.
- Run
cloudflaredunder systemd so the tunnel restarts after reboot. - Log both tunnel health and Autohand Code job output.