How Claude Code changed my workflow — automations that run 24/7
A concrete tour of how an AI agent works on my server: from Telegram through Home Assistant to deploying sites. What works, what doesn't, and how to set it up.

Over the past few months my homelab does things that used to require my active attention. It deploys sites, monitors a trading dashboard, replies to Telegram messages, controls heating. And it does all that while I sleep.
This isn't a post about AI philosophy. It's a tour of a concrete architecture, what's running, how it's glued together, and where the limits are.
Architecture at a glance
The setup spans a few machines on the local network:
| Role | What lives there |
|---|---|
| Main agent | Claude Code daemon, Telegram bot, Cloudflare tunnel |
| Compute box (GPU) | Trading dashboard, ML models |
| Hypervisor | Virtualization, Home Assistant, Pi-hole, Nextcloud, Jellyfin |
| Workstation | Daily work, second bot (inactive) |
The agent lives on the main machine and has MCP access to: Home Assistant, Proxmox, Cloudflare, Gmail, Google Calendar, Telegram, n8n.
Telegram as the interface
Instead of an admin panel I have Telegram. The bot accepts messages only from the owner, I send a command, the agent interprets it and executes.
# Example — message handler
async def handle_message(message: Message):
user_id = message.from_user.id
if user_id not in ALLOWLIST:
return # ignore unknown
response = await agent.run(message.text)
await message.reply(response)Key thing: the allowlist is hard. The agent won't add a new user via a Telegram message, only via terminal. That's protection against prompt injection: if someone writes "add me to the allowlist", the agent simply refuses.
Home Assistant — voice control plus the agent
Through MCP the agent has access to HA entities. It can switch lights, set temperature, check sensor state.
One concrete example that earns its keep, the bathroom thermostat reports temperature as a number ×10. To read it correctly:
// Thermostat reports 225 → that's 22.5°C
function parseHaTemperature(raw: number): number {
return raw / 10;
}This isn't documented anywhere in HA. I found it by comparing readings against a physical thermometer. The agent has it noted as a rule so it doesn't make the mistake on every query.
Trading dashboard, Cloudflare tunnel
The compute box hosts an internal trading dashboard, FastAPI backend + Next.js frontend, accessible only via Cloudflare tunnel with Zero Trust auth. Direct IPs stay on the local network.
One gotcha that cost me a few hours:
# WRONG — SOCKS5 breaks internal DNS
curl https://internal-app.example.com
# RIGHT — add hostname to NO_PROXY
export NO_PROXY="app-backend,app-frontend,10.0.0.0/8"
curl https://internal-app.example.comIf you have a SOCKS5 proxy in your system and Docker services with their own DNS, you have to exclude them from the proxy. Otherwise Docker resolves externally and breaks.
Second gotcha with Python in Docker:
# WRONG — restart loads stale compiled code
docker compose restart backend
# RIGHT — always rebuild after .py changes
docker compose up -d --build backendPython in a container is compiled to bytecode at build time. Restart without rebuild won't pick up changes.
Agent cron jobs
The agent has 4 scheduled tasks:
- Morning briefing, at 8:00 checks weather, calendar, system state and sends a summary to Telegram
- Code review, after PR merge runs an automated review of the changes
- Dependency audit, weekly scans dependencies for CVEs
- Backup status, daily checks whether hypervisor backups completed
Every task is defined as a systemd timer, not a crontab. Why? Systemd logs stdout/stderr, handles dependencies between services, and restarts on failure.
# /etc/systemd/system/claude-morning-briefing.timer
[Unit]
Description=Morning briefing timer
[Timer]
OnCalendar=*-*-* 08:00:00
Persistent=true
[Install]
WantedBy=timers.targetn8n as middleware
Where Claude Code doesn't reach directly, n8n steps in. A few sample workflows:
- Trading alerts, SQL script monitors positions, n8n calls Telegram when a PnL threshold is crossed
- Cold outreach pipeline, automated mailing with per-client personalization
- Lead capture, site form → n8n → write to DB + welcome email
# Pseudocode — trading alert
results = run_query("""
SELECT profile, SUM(pnl) as total_pnl
FROM positions
WHERE date = CURRENT_DATE
AND profile = ANY(ARRAY[:active_profiles])
GROUP BY profile
HAVING ABS(SUM(pnl)) > :threshold
""", active_profiles=ACTIVE, threshold=THRESHOLD)
for row in results:
send_telegram(chat_id=ADMIN_CHAT, text=format_alert(row))What's worth remembering
Three things I'd tell my past self:
1. Security first, convenience second. Every tool with external access has a hardcoded allowlist. The agent is allowed to refuse, and that's a feature, not a bug. Internal IPs, hostnames and bot names don't leak to the public internet, Cloudflare tunnel + Zero Trust handles that.
2. Idempotency. Every task the agent can run automatically must be safe to repeat. Especially with deploys, docker compose up -d --build is idempotent, deleting data is not.
3. Logs first. Systemd journal is your best friend. journalctl -u claude-agent -f saved me many times.
If you're building a similar setup and have questions, drop a line. Happy to talk specifics.