n8n GitHub Issue Automation

Webhook-triggered GitHub issue processing via n8n + Cloudflare Tunnel + local Claude agent.


Overview

When a GitHub issue receives the label auto or auto-local, a webhook fires to n8n on the Hostinger VPS. n8n routes the event to the appropriate worker environment:

  • auto-local — tunnelled to the local dev machine (Windows 10) via Cloudflare Tunnel, where a Python listener spawns claude /process-issues-remote.
  • auto — sent directly to the claude-proxy service on vps-h1, which runs claude -p "process issue #N".

Both paths use Supabase agent_sessions with claim_task() (FOR UPDATE SKIP LOCKED) to prevent double-processing when multiple environments are active.


Architecture

GitHub issue labeled "auto" / "auto-local"
  │
  └─→ POST → n8n.vps-h1 /webhook/github-issues
               │ responds 200 immediately
               │
               ├── label = "auto-local"
               │     └─→ Cloudflare Tunnel → localhost:3500 (local-listener.py)
               │                               ├─ INSERT agent_sessions (worker_env='local')
               │                               └─ spawn: claude /process-issues-remote
               │                                           └─ claim_task() [FOR UPDATE SKIP LOCKED]
               │                                               └─ parallel worktrees (MAX=4)
               │
               └── label = "auto"
                     └─→ claude-proxy:9999 (vps-h1)
                               └─ claude -p "process issue #N"

Label Routing

LabelRoutes toworker_env
autoVPS claude-proxy (vps-h1, port 9999)vps-h1
auto-localLocal dev machine via Cloudflare Tunnel (port 3500)local

Supabase Coordination

agent_sessions table

Every pipeline run inserts a row into agent_sessions before spawning Claude. This provides:

  • Visibility — active/completed sessions visible in Supabase dashboard
  • Deduplicationclaim_task() uses FOR UPDATE SKIP LOCKED so concurrent workers skip already-claimed issues
  • Capacity capMAX_PARALLEL=4 enforced by counting status='active' rows before accepting new work

worker_env column

The worker_env column (type text) identifies which environment created the session.

ValueEnvironment
localLocal dev machine (Windows 10, radieu)
vps-h1Hostinger VPS — claude-proxy
vps-i1IONOS VPS — AI-Dev-IO1
ciGitHub Actions runner

The column was added by migration 002_agent_worker_env.sql (see file inventory below).

claim_task() flow

-- Simplified pseudocode
BEGIN;
  SELECT id FROM agent_sessions
  WHERE status = 'pending'
  LIMIT 1
  FOR UPDATE SKIP LOCKED;
 
  -- If row found AND active_count < MAX_PARALLEL:
  UPDATE agent_sessions SET status = 'active', worker_env = $1 WHERE id = $2;
COMMIT;

If MAX_PARALLEL (4) active sessions already exist, the caller receives no row and must back off.


File Inventory

FilePurpose
monitoring/supabase/migrations/002_agent_worker_env.sqlAdds worker_env column to agent_sessions
infra-src/n8n-workflows/github-auto-trigger.jsonn8n workflow — import via n8n UI
infra-src/github-agent/local-listener.pyLocal HTTP server on port 3500
infra-src/github-agent/cloudflared-config.yml.templateCloudflare Tunnel config template
scripts/setup-cloudflare-tunnel.ps1One-shot tunnel setup (idempotent, Windows PowerShell)

Setup Checklist

Complete these steps in order on first setup.

  • Apply SQL migration — run 002_agent_worker_env.sql against the Supabase project (mwkqmgadqnkkihjdeqsi) via the Supabase SQL editor or supabase db push
  • Configure Cloudflare Tunnel — from the local dev machine:
    cloudflared tunnel login   # one-time browser auth
    .\scripts\setup-cloudflare-tunnel.ps1
  • Start the tunnel (keep running in a terminal or add as a Windows service):
    cloudflared tunnel --config "$env:USERPROFILE\.cloudflared\local-dev-config.yml" run local-dev
  • Set environment variables and start the local listener:
    $env:PROXY_SECRET             = "your-secret"          # must match n8n GITHUB_TRIGGER_SECRET
    $env:SUPABASE_URL             = "https://mwkqmgadqnkkihjdeqsi.supabase.co"
    $env:SUPABASE_SERVICE_ROLE_KEY = "your-service-role-key"
    python infra-src/github-agent/local-listener.py
  • Import n8n workflow — in the n8n UI on vps-h1, import infra-src/n8n-workflows/github-auto-trigger.json; set the GITHUB_TRIGGER_SECRET credential/env var to match PROXY_SECRET; activate the workflow
  • Register GitHub webhook — in radieu/p24-infra → Settings → Webhooks → Add webhook:
    • URL: https://n8n.vps-h1.infra.zintegrowana.online/webhook/github-issues
    • Content type: application/json
    • Events: Issues only
  • Smoke test — create a throwaway issue in radieu/p24-infra, add label auto-local, then verify:
    • HTTP 202 logged by local-listener.py
    • New row in agent_sessions table (Supabase dashboard → Table Editor → agent_sessions)
    • claude /process-issues-remote process spawned (visible in Task Manager / ps aux)

Environment Variables

Required by infra-src/github-agent/local-listener.py:

VariableDescription
PROXY_SECRETShared secret validated against the X-Proxy-Secret header sent by n8n. Must match the value configured in the n8n workflow.
SUPABASE_URLFull Supabase project URL, e.g. https://mwkqmgadqnkkihjdeqsi.supabase.co
SUPABASE_SERVICE_ROLE_KEYSupabase service_role key — required for server-side INSERT into agent_sessions (bypasses RLS)

The listener will exit immediately at startup if any of these variables are absent.