{"workflow":{"id":13943,"name":"Manage Claude Code sessions from Matrix with YouTrack and GitLab","views":74,"recentViews":1,"totalViews":74,"createdAt":"2026-03-08T14:02:57.855Z","description":"# Manage AI coding sessions from Matrix with YouTrack and GitLab\n\n## Who is this for\nDevelopment teams using Claude Code who want a chat-ops interface for\nproject management. Instead of SSH-ing into a server to run Claude,\nyour whole team interacts with it through a Matrix chat room — with\nissue tracking and CI/CD pipeline visibility built in.\n\n## What it does\n- Polls a Matrix room every 30 seconds and routes commands to handlers\n- Forwards messages to Claude Code sessions via SSH, posts responses back\n- Manages multiple concurrent sessions — start, pause, resume, switch, end\n- Updates YouTrack issues directly from chat (change state, post comments, query status)\n- Fetches GitLab CI pipeline status and logs, retries failed pipelines\n- Reports server load and Claude process status\n- Tracks sessions in SQLite with lock-based concurrency and cooldown guards\n\n## Template Image\n![Screenshot from 20260312 183201.png](fileId:4905)\n\n\n## How it works\nThe workflow polls a Matrix room for new messages. A command router\nparses `!commands` and dispatches them to the right handler:\n\n- **Regular messages** resume an active Claude Code session and post the response\n- **!session** — manage sessions (current, list, done, cancel, pause, resume, switch, log)\n- **!issue** — update YouTrack issues (status, info, start, stop, verify, done, close, comment)\n- **!pipeline** — fetch GitLab CI pipeline status and logs, retry failed pipelines\n- **!system** — report server load and running processes\n- **!help** — show command reference\n\n## Requirements\n- **Matrix** homeserver with a bot account (E2EE must be disabled —\n  n8n cannot decrypt Matrix messages)\n- **YouTrack** instance with API token\n- **GitLab** instance with API token\n- **Linux server** with Claude Code CLI installed, accessible via SSH\n- **n8n** self-hosted with SSH private key credentials configured\n\n&gt; This is a self-hosted template. It requires your own infrastructure —\n&gt; it will not run on n8n Cloud without a server accessible via SSH.\n\n## How to set up\n1. Import the workflow into n8n\n2. Edit the **Gateway Config** Set node with your URLs and paths\n3. Set `YOUTRACK_TOKEN` and `GITLAB_TOKEN` as environment variables\n   on your server (not in the workflow)\n4. Create n8n credentials: SSH Private Key and HTTP Header Auth\n   (Matrix bot token as `Authorization: Bearer &lt;token&gt;`)\n5. Create the SQLite database on your server:\n```sql\nCREATE TABLE sessions (\n  issue_id TEXT PRIMARY KEY, issue_title TEXT,\n  session_id TEXT, started_at TEXT, last_active TEXT,\n  message_count INTEGER DEFAULT 0, paused INTEGER DEFAULT 0,\n  is_current INTEGER DEFAULT 0\n);\nCREATE TABLE queue (\n  id INTEGER PRIMARY KEY AUTOINCREMENT,\n  issue_id TEXT, message TEXT, queued_at TEXT\n);\nCREATE TABLE session_log (\n  id INTEGER PRIMARY KEY AUTOINCREMENT,\n  issue_id TEXT, issue_title TEXT, session_id TEXT,\n  started_at TEXT, ended_at TEXT,\n  message_count INTEGER, outcome TEXT\n);\n```\n\n6. Activate the workflow\n\n## How to customize\n- **Add commands** — add a case to the Command Router Switch node\n- **Change polling interval** — edit the Schedule Trigger node\n- **Swap issue tracker** — replace YouTrack API calls with Jira or Linear\n- **Swap chat platform** — replace Matrix HTTP nodes with Slack or Discord\n- **Add pipeline detail** — extend Handle Pipeline to fetch individual job logs\n","workflow":{"id":"UGhTOwk0sz0gm8PU","meta":{"instanceId":"0dd3ae188cfd2b96467338af7f80668f3761b218fde5df5eff29cb57f9644576"},"name":"Manage AI coding sessions from Matrix with YouTrack and GitLab","tags":[],"nodes":[{"id":"7c31b18c","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[208,-352],"parameters":{"color":4,"width":540,"height":520,"content":"## Manage AI coding sessions from Matrix\nA chat-ops bridge between Matrix, Claude Code,\nYouTrack, and GitLab. Your team talks to an AI\ncoding assistant from a chat room — with issue\ntracking and CI/CD visibility built in.\n\n## How it works\n1. Polls a Matrix room every 30s for messages\n2. Routes `!commands` to the matching handler\n3. Forwards messages to Claude Code via SSH\n4. Posts Claude's response back to Matrix\n5. Syncs session state with YouTrack issues\n\n## Setup steps\n1. Import and edit the **Gateway Config** node\n2. Create n8n credentials: SSH + Matrix token\n3. Set `YOUTRACK_TOKEN` and `GITLAB_TOKEN`\n4. Create the SQLite database (see description)\n5. Activate the workflow"},"typeVersion":1},{"id":"4ceda743","name":"Sticky Note 4fa9","type":"n8n-nodes-base.stickyNote","position":[176,224],"parameters":{"width":260,"height":228,"content":"### 1. Configuration\nUser-configurable variables."},"typeVersion":1},{"id":"277ed962","name":"Sticky Note 7417","type":"n8n-nodes-base.stickyNote","position":[464,224],"parameters":{"width":960,"height":228,"content":"### 2. Matrix polling\nPolls Matrix /sync every 30 seconds, extracts new messages, filters empty batches."},"typeVersion":1},{"id":"874e52f1","name":"Sticky Note 782c","type":"n8n-nodes-base.stickyNote","position":[1472,224],"parameters":{"width":500,"height":420,"content":"### 3. Command routing\nParses `!commands`, routes to handlers."},"typeVersion":1},{"id":"8489758d","name":"Sticky Note c9e3","type":"n8n-nodes-base.stickyNote","position":[2016,-304],"parameters":{"width":1576,"height":374,"content":"### 4. Message handler\nChecks lock, reads session, resumes Claude via SSH, posts response to Matrix."},"typeVersion":1},{"id":"2e04a109","name":"Sticky Note 6e7b","type":"n8n-nodes-base.stickyNote","position":[2016,128],"parameters":{"width":592,"height":1248,"content":"### 5. Command handlers\n**!session** current, list, done, cancel, pause, resume\n**!issue** status, info, start, verify, done, comment\n**!pipeline** status, logs, retry\n**!system** status | **!help** reference"},"typeVersion":1},{"id":"7c36c474","name":"Sticky Note dbbd","type":"n8n-nodes-base.stickyNote","position":[2656,496],"parameters":{"width":760,"height":288,"content":"### 6. Response and session end\nPosts output to Matrix. Archives session to SQLite on done."},"typeVersion":1},{"id":"gateway-config","name":"Gateway Config","type":"n8n-nodes-base.set","position":[208,304],"parameters":{"options":{},"assignments":{"assignments":[{"id":"ssh-host","name":"SSH_HOST","type":"string","value":"your-server-hostname"},{"id":"e402df7c","name":"MATRIX_HOMESERVER","type":"string","value":"https://your-matrix-server.com"},{"id":"7a2638f9","name":"MATRIX_ROOM_ID","type":"string","value":"!yourRoomId:your-matrix-server.com"},{"id":"f020d2f8","name":"MATRIX_BOT_USER","type":"string","value":"@bot:your-matrix-server.com"},{"id":"dc840315","name":"YOUTRACK_URL","type":"string","value":"https://your-youtrack-instance.com"},{"id":"0e88ef8d","name":"GITLAB_URL","type":"string","value":"https://your-gitlab-instance.com"},{"id":"0ef605c8","name":"CLAUDE_PROJECT_PATH","type":"string","value":"/home/user/your-project"},{"id":"e0482d36","name":"CLAUDE_BINARY","type":"string","value":"/home/user/.local/bin/claude"},{"id":"690b2fc6","name":"DB_PATH","type":"string","value":"/home/user/your-project/claude-context/gateway.db"},{"id":"e8bcc6ff","name":"CONTEXT_DIR","type":"string","value":"/home/user/your-project/claude-context"},{"id":"393a34b8","name":"ISSUE_PREFIX","type":"string","value":"PROJ"},{"id":"8d185b9c","name":"COOLDOWN_TTL","type":"string","value":"30"}]}},"typeVersion":3.4},{"id":"poll-trigger","name":"Poll Every 30s","type":"n8n-nodes-base.scheduleTrigger","position":[512,304],"parameters":{"rule":{"interval":[{"field":"seconds"}]}},"typeVersion":1.2},{"id":"get-sync-token","name":"Get Sync Token","type":"n8n-nodes-base.code","position":[704,304],"parameters":{"jsCode":"const staticData = $getWorkflowStaticData('global');\nconst sinceToken = staticData.matrixSinceToken || '';\nreturn [{ json: { sinceToken, ...$('Gateway Config').first().json } }];"},"typeVersion":2},{"id":"poll-matrix","name":"Poll Matrix Sync","type":"n8n-nodes-base.httpRequest","position":[912,304],"parameters":{"url":"={{ $json.MATRIX_HOMESERVER }}/_matrix/client/v3/sync?timeout=0&filter={\"room\":{\"rooms\":[\"{{ $json.MATRIX_ROOM_ID }}\"],\"timeline\":{\"limit\":10}}}{{ $json.sinceToken ? '&since=' + $json.sinceToken : '' }}","options":{"timeout":15000},"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"credentials":{"httpHeaderAuth":{"id":"MATRIX_CRED_ID","name":"Matrix Bot Token"}},"typeVersion":4.2},{"id":"extract-messages","name":"Extract Messages","type":"n8n-nodes-base.code","position":[1104,304],"parameters":{"jsCode":"const config = $('Gateway Config').first().json;\nconst syncData = $input.first().json;\nconst staticData = $getWorkflowStaticData('global');\n\n// Save sync token for next poll\nif (syncData.next_batch) staticData.matrixSinceToken = syncData.next_batch;\n\n// Extract messages from timeline\nconst roomId = config.MATRIX_ROOM_ID;\nconst rooms = syncData.rooms?.join || {};\nconst room = rooms[roomId] || syncData.rooms?.join?.[Object.keys(syncData.rooms?.join || {})[0]];\nconst events = room?.timeline?.events || [];\n\nconst lastTs = staticData.lastProcessedTimestamp || 0;\nconst botUser = config.MATRIX_BOT_USER;\n\nconst messages = events\n  .filter(e => e.type === 'm.room.message'\n    && e.sender !== botUser\n    && e.origin_server_ts > lastTs)\n  .map(e => ({\n    messageText: e.content?.body || '',\n    sender: e.sender,\n    timestamp: e.origin_server_ts\n  }));\n\nif (messages.length > 0) {\n  staticData.lastProcessedTimestamp = Math.max(...messages.map(m => m.timestamp));\n}\n\nreturn messages.length > 0\n  ? [{ json: { ...messages[0], ...config } }]\n  : [];"},"typeVersion":2},{"id":"has-messages","name":"Has Messages?","type":"n8n-nodes-base.if","position":[1312,304],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"c8b1ae50","operator":{"type":"string","operation":"isNotEmpty","singleValue":true},"leftValue":"={{ $json.messageText }}","rightValue":""}]}},"typeVersion":2},{"id":"detect-command","name":"Detect Command","type":"n8n-nodes-base.code","position":[1504,304],"parameters":{"jsCode":"const config = $('Gateway Config').first().json;\nconst messageText = $input.first().json.messageText || '';\nconst trimmed = messageText.trim();\nconst lower = trimmed.toLowerCase();\nconst base64Message = Buffer.from(messageText).toString('base64');\n\n// Check for PREFIX: message format (e.g. PROJ-4: do something)\nconst prefixMatch = trimmed.match(/^([A-Z]+-\\d+):\\s*([\\s\\S]*)$/);\nif (prefixMatch) {\n  const prefix = prefixMatch[1];\n  const strippedText = prefixMatch[2].trim();\n  if (!strippedText) {\n    return [{ json: { ...config, command: 'empty', matrixBody: JSON.stringify({ msgtype: 'm.notice', body: 'Message body is empty. Usage: ' + prefix + ': <your message>' }) } }];\n  }\n  const base64Stripped = Buffer.from(strippedText).toString('base64');\n  return [{ json: { ...config, command: 'message', sub: '', args: [], messageText: strippedText, base64Message: base64Stripped, prefix } }];\n}\n\nif (!lower.startsWith('!')) {\n  if (!trimmed) return [{ json: { ...config, command: 'empty', matrixBody: JSON.stringify({ msgtype: 'm.notice', body: 'Message cannot be empty.' }) } }];\n  return [{ json: { ...config, command: 'message', sub: '', args: [], messageText, base64Message, prefix: '' } }];\n}\n\nconst lowerParts = lower.slice(1).split(/\\s+/);\nconst origParts = trimmed.slice(1).split(/\\s+/);\nconst command = lowerParts[0] || '';\nconst sub = lowerParts[1] || '';\nconst args = origParts.slice(2);\n\n// Legacy aliases\nconst resolved = { done: 'session', cancel: 'session', status: 'session' }[command] || command;\nconst resolvedSub = { done: 'done', cancel: 'cancel', status: 'current' }[command] || sub;\n\nreturn [{ json: { ...config, command: resolved, sub: resolvedSub, args, messageText, base64Message, prefix: '' } }];"},"typeVersion":2},{"id":"command-router","name":"Command Router","type":"n8n-nodes-base.switch","position":[1760,304],"parameters":{"rules":{"values":[{"outputKey":"message","conditions":{"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.command }}","rightValue":"message"}]},"renameOutput":true},{"outputKey":"session","conditions":{"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.command }}","rightValue":"session"}]},"renameOutput":true},{"outputKey":"help","conditions":{"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.command }}","rightValue":"help"}]},"renameOutput":true},{"outputKey":"issue","conditions":{"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.command }}","rightValue":"issue"}]},"renameOutput":true},{"outputKey":"pipeline","conditions":{"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.command }}","rightValue":"pipeline"}]},"renameOutput":true},{"outputKey":"system","conditions":{"combinator":"and","conditions":[{"operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.command }}","rightValue":"system"}]},"renameOutput":true}]},"options":{"fallbackOutput":"extra"}},"typeVersion":3},{"id":"check-lock","name":"Check Lock","type":"n8n-nodes-base.ssh","position":[2064,-112],"parameters":{"command":"={{ 'GW=\"' + $json.CONTEXT_DIR + '\" && if [ -f $GW/gateway.lock ] && [ $(( $(date +%s) - $(stat -c %Y $GW/gateway.lock) )) -lt 600 ]; then echo LOCKED; else rm -f $GW/gateway.lock && echo FREE; fi' }}","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"SSH_CRED_ID","name":"Server SSH Key"}},"typeVersion":1},{"id":"is-locked","name":"Is Locked?","type":"n8n-nodes-base.if","position":[2272,-112],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"02be178d","operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.stdout.trim() }}","rightValue":"LOCKED"}]}},"typeVersion":2},{"id":"post-busy","name":"Post Busy Notice","type":"n8n-nodes-base.httpRequest","position":[2608,-240],"parameters":{"url":"={{ $('Gateway Config').first().json.MATRIX_HOMESERVER }}/_matrix/client/v3/rooms/{{ $('Gateway Config').first().json.MATRIX_ROOM_ID }}/send/m.room.message/busy-{{ Date.now() }}","method":"PUT","options":{},"jsonBody":"={{ JSON.stringify({ msgtype: 'm.notice', body: 'Claude is busy processing a previous message. Your message has been queued.' }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"credentials":{"httpHeaderAuth":{"id":"MATRIX_CRED_ID","name":"Matrix Bot Token"}},"typeVersion":4.2},{"id":"read-session","name":"Read Session & Acquire Lock","type":"n8n-nodes-base.ssh","position":[2496,-112],"parameters":{"command":"={{ 'DB=\"' + $('Gateway Config').first().json.DB_PATH + '\" && GW=\"' + $('Gateway Config').first().json.CONTEXT_DIR + '\" && echo locked > $GW/gateway.lock && sqlite3 $DB \"SELECT json_object(\\'sessionId\\',session_id,\\'issueId\\',issue_id,\\'issueTitle\\',issue_title) FROM sessions WHERE is_current=1 LIMIT 1;\" 2>/dev/null' }}","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"SSH_CRED_ID","name":"Server SSH Key"}},"typeVersion":1,"continueOnFail":true},{"id":"resume-claude","name":"Resume Claude Session","type":"n8n-nodes-base.ssh","position":[2720,-112],"parameters":{"command":"=CLAUDE_BIN=\"{{ $(\"Gateway Config\").first().json.CLAUDE_BINARY }}\"\nPROJECT=\"{{ $(\"Gateway Config\").first().json.CLAUDE_PROJECT_PATH }}\"\nSESSION_RAW=\"{{ $(\"Read Session & Acquire Lock\").first().json.stdout || '' }}\"\nMSG_B64=\"{{ $(\"Detect Command\").first().json.base64Message }}\"\n\nSESSION_RAW=$(echo \"$SESSION_RAW\" | tr -d '\\n')\n[ -z \"$SESSION_RAW\" ] && echo \"NO_SESSION\" && exit 0\n\nSID=$(echo \"$SESSION_RAW\" | python3 -c \"import sys,json; print(json.load(sys.stdin).get('sessionId',''))\" 2>/dev/null)\n[ -z \"$SID\" ] && echo \"NO_SESSION\" && exit 0\n\nunset CLAUDECODE\ncd \"$PROJECT\"\ntimeout 300 \"$CLAUDE_BIN\" -r \"$SID\" -p \"$(printf '%s' \"$MSG_B64\" | base64 -d)\" --output-format json --dangerously-skip-permissions 2>&1","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"SSH_CRED_ID","name":"Server SSH Key"}},"typeVersion":1,"continueOnFail":true},{"id":"parse-response","name":"Parse Claude Response","type":"n8n-nodes-base.code","position":[2944,-112],"parameters":{"jsCode":"const config = $('Gateway Config').first().json;\nconst stdout = $input.first().json.stdout || '';\nconst stderr = $input.first().json.stderr || '';\nconst output = (stdout.trim() || stderr.trim());\n\nif (output === 'NO_SESSION') {\n  const body = 'No active Claude session. Start one via YouTrack or !issue start <id>.';\n  return [{ json: { matrixBody: JSON.stringify({ msgtype: 'm.notice', body }), ...config } }];\n}\n\nlet result;\ntry {\n  const parsed = JSON.parse(output);\n  result = parsed.result || output.substring(0, 4000);\n} catch(e) {\n  result = output.substring(0, 4000) || 'No response from Claude.';\n}\n\nconst body = result;\nconst matrixBody = JSON.stringify({ msgtype: 'm.text', body });\nreturn [{ json: { matrixBody, ...config } }];"},"typeVersion":2},{"id":"post-response","name":"Post Response to Matrix","type":"n8n-nodes-base.httpRequest","position":[3152,-112],"parameters":{"url":"={{ $json.MATRIX_HOMESERVER }}/_matrix/client/v3/rooms/{{ $json.MATRIX_ROOM_ID }}/send/m.room.message/resp-{{ Date.now() }}","method":"PUT","options":{},"jsonBody":"={{ $json.matrixBody }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"credentials":{"httpHeaderAuth":{"id":"MATRIX_CRED_ID","name":"Matrix Bot Token"}},"typeVersion":4.2},{"id":"release-lock","name":"Release Lock","type":"n8n-nodes-base.ssh","position":[3376,-112],"parameters":{"command":"={{ 'rm -f \"' + $('Gateway Config').first().json.CONTEXT_DIR + '/gateway.lock\"' }}","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"SSH_CRED_ID","name":"Server SSH Key"}},"typeVersion":1,"continueOnFail":true},{"id":"handle-session","name":"Handle Session Command","type":"n8n-nodes-base.ssh","position":[2064,272],"parameters":{"command":"=SUB=\"{{ $(\"Detect Command\").first().json.sub || 'current' }}\"\nRAW_ARG0=\"{{ $(\"Detect Command\").first().json.args[0] || '' }}\"\nDB=\"{{ $(\"Gateway Config\").first().json.DB_PATH }}\"\nGW=\"{{ $(\"Gateway Config\").first().json.CONTEXT_DIR }}\"\n\nARG0=$(echo \"$RAW_ARG0\" | tr '[:lower:]' '[:upper:]')\n\ncase \"$SUB\" in\n  current)\n    ROW=$(sqlite3 \"$DB\" \"SELECT json_object('issue_id',issue_id,'session_id',session_id,'started_at',started_at,'message_count',message_count,'paused',paused) FROM sessions WHERE is_current=1 LIMIT 1;\")\n    echo \"SESSION:${ROW:-NO_SESSION}\"\n    ;;\n  list)\n    sqlite3 \"$DB\" \"SELECT json_group_array(json_object('issue_id',issue_id,'started_at',started_at,'message_count',message_count,'paused',paused,'is_current',is_current)) FROM sessions ORDER BY is_current DESC;\" 2>/dev/null\n    ;;\n  done)\n    if [ -n \"$ARG0\" ]; then W=\"issue_id='$ARG0'\"; else W=\"is_current=1\"; fi\n    sqlite3 \"$DB\" \"SELECT json_object('session_id',session_id,'issue_id',issue_id,'issue_title',issue_title) FROM sessions WHERE $W LIMIT 1;\" 2>/dev/null\n    ;;\n  cancel)\n    if [ -n \"$ARG0\" ]; then W=\"issue_id='$ARG0'\"; else W=\"is_current=1\"; fi\n    ISSUE=$(sqlite3 \"$DB\" \"SELECT issue_id FROM sessions WHERE $W LIMIT 1;\")\n    if [ -n \"$ISSUE\" ]; then\n      pkill -f claude 2>/dev/null\n      rm -f \"$GW/gateway.lock\"\n      sqlite3 \"$DB\" \"DELETE FROM sessions WHERE issue_id='$ISSUE'; DELETE FROM queue WHERE issue_id='$ISSUE';\"\n      echo \"CANCELLED:$ISSUE\"\n    else\n      echo \"NO_SESSION\"\n    fi\n    ;;\n  pause)\n    if [ -n \"$ARG0\" ]; then W=\"issue_id='$ARG0'\"; else W=\"is_current=1\"; fi\n    ISSUE=$(sqlite3 \"$DB\" \"SELECT issue_id FROM sessions WHERE $W LIMIT 1;\")\n    if [ -n \"$ISSUE\" ]; then\n      sqlite3 \"$DB\" \"UPDATE sessions SET paused=1 WHERE issue_id='$ISSUE';\"\n      echo \"PAUSED:$ISSUE\"\n    else\n      echo \"NO_SESSION\"\n    fi\n    ;;\n  resume)\n    if [ -n \"$ARG0\" ]; then W=\"issue_id='$ARG0'\"; else W=\"is_current=1\"; fi\n    ISSUE=$(sqlite3 \"$DB\" \"SELECT issue_id FROM sessions WHERE $W LIMIT 1;\")\n    if [ -n \"$ISSUE\" ]; then\n      sqlite3 \"$DB\" \"UPDATE sessions SET paused=0 WHERE issue_id='$ISSUE';\"\n      echo \"RESUMED:$ISSUE\"\n    else\n      echo \"NO_SESSION\"\n    fi\n    ;;\n  *) echo \"UNKNOWN_SUB:$SUB\" ;;\nesac","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"SSH_CRED_ID","name":"Server SSH Key"}},"typeVersion":1,"continueOnFail":true},{"id":"format-session","name":"Format Session Response","type":"n8n-nodes-base.code","position":[2272,272],"parameters":{"jsCode":"const config = $('Gateway Config').first().json;\nconst stdout = $input.first().json.stdout || '';\nconst sub = $('Detect Command').first().json.sub || 'current';\nconst output = stdout.trim();\nlet body = '';\n\nif (sub === 'current') {\n  const raw = output.replace('SESSION:', '');\n  if (!raw || raw === 'NO_SESSION') body = 'No active session.';\n  else {\n    try {\n      const s = JSON.parse(raw);\n      body = 'Current session:\\nIssue: ' + s.issue_id + '\\nMessages: ' + s.message_count + '\\nPaused: ' + (s.paused ? 'Yes' : 'No');\n    } catch(e) { body = 'Error: ' + raw.substring(0, 200); }\n  }\n} else if (sub === 'list') {\n  try {\n    const rows = JSON.parse(output || '[]');\n    body = rows.length ? rows.map((s, i) => (i+1) + '. ' + s.issue_id + (s.is_current ? ' (current)' : '')).join('\\n') : 'No active sessions.';\n  } catch(e) { body = 'Error: ' + output.substring(0, 200); }\n} else if (sub === 'done') {\n  if (!output) body = 'No active session to end.';\n  else {\n    try { const s = JSON.parse(output); body = s.session_id ? 'Ending session for ' + s.issue_id + '...' : 'No active session.'; } catch(e) { body = 'No active session.'; }\n  }\n} else if (sub === 'cancel') {\n  body = output.startsWith('CANCELLED:') ? 'Session ' + output.split(':')[1] + ' cancelled.' : output === 'NO_SESSION' ? 'No session to cancel.' : output;\n} else if (sub === 'pause') {\n  body = output.startsWith('PAUSED:') ? 'Session ' + output.split(':')[1] + ' paused.' : output === 'NO_SESSION' ? 'No session to pause.' : output;\n} else if (sub === 'resume') {\n  body = output.startsWith('RESUMED:') ? 'Session ' + output.split(':')[1] + ' resumed.' : output === 'NO_SESSION' ? 'No session to resume.' : output;\n} else { body = 'Unknown: ' + sub; }\n\nconst matrixBody = JSON.stringify({ msgtype: 'm.notice', body });\nreturn [{ json: { matrixBody, ...config } }];"},"typeVersion":2},{"id":"handle-issue","name":"Handle Issue Command","type":"n8n-nodes-base.ssh","position":[2064,480],"parameters":{"command":"=SUB=\"{{ $(\"Detect Command\").first().json.sub || 'status' }}\"\nRAW_ARG0=\"{{ $(\"Detect Command\").first().json.args[0] || '' }}\"\nYT_URL=\"{{ $(\"Gateway Config\").first().json.YOUTRACK_URL }}\"\n\nARG0=$(echo \"$RAW_ARG0\" | tr '[:lower:]' '[:upper:]')\n\ncase \"$SUB\" in\n  status)\n    curl -s --max-time 10 \\\n      -H \"Authorization: Bearer $YT_TOKEN\" \\\n      \"$YT_URL/api/issues?query=State:+%7BIn+Progress%7D&fields=idReadable,summary,customFields(name,value(name))\" 2>&1\n    ;;\n  info)\n    [ -z \"$ARG0\" ] && echo \"USAGE: !issue info <id>\" && exit 0\n    curl -s --max-time 10 \\\n      -H \"Authorization: Bearer $YT_TOKEN\" \\\n      \"$YT_URL/api/issues/$ARG0?fields=idReadable,summary,description,customFields(name,value(name))\" 2>&1\n    ;;\n  *) echo \"Available: !issue status, !issue info <id>\" ;;\nesac","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"SSH_CRED_ID","name":"Server SSH Key"}},"typeVersion":1,"continueOnFail":true},{"id":"format-issue","name":"Format Issue Response","type":"n8n-nodes-base.code","position":[2272,432],"parameters":{"jsCode":"const config = $('Gateway Config').first().json;\nconst stdout = $input.first().json.stdout || '';\nconst output = stdout.trim();\nlet body = '';\n\ntry {\n  const data = JSON.parse(output);\n  if (Array.isArray(data)) {\n    body = data.length === 0 ? 'No issues in progress.' : data.map(i => i.idReadable + ': ' + (i.summary || '—')).join('\\n');\n  } else if (data.idReadable) {\n    body = data.idReadable + ': ' + (data.summary || '—') + '\\n' + (data.description || 'No description').substring(0, 500);\n  } else { body = output.substring(0, 1000); }\n} catch(e) { body = output || 'No response from YouTrack.'; }\n\nconst matrixBody = JSON.stringify({ msgtype: 'm.notice', body });\nreturn [{ json: { matrixBody, ...config } }];"},"typeVersion":2},{"id":"handle-pipeline","name":"Handle Pipeline Command","type":"n8n-nodes-base.ssh","position":[2064,640],"parameters":{"command":"=SUB=\"{{ $(\"Detect Command\").first().json.sub || 'status' }}\"\nGL_URL=\"{{ $(\"Gateway Config\").first().json.GITLAB_URL }}\"\n\ncase \"$SUB\" in\n  status)\n    curl -s --max-time 10 \\\n      -H \"PRIVATE-TOKEN: $GL_TOKEN\" \\\n      \"$GL_URL/api/v4/projects?membership=true&simple=true\" | \\\n      python3 -c \"import sys,json; projects=json.load(sys.stdin); [print(p['name'] + ': ' + p.get('default_branch','main')) for p in projects[:10]]\" 2>&1\n    ;;\n  *) echo \"Available: !pipeline status\" ;;\nesac","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"SSH_CRED_ID","name":"Server SSH Key"}},"typeVersion":1,"continueOnFail":true},{"id":"format-pipeline","name":"Format Pipeline Response","type":"n8n-nodes-base.code","position":[2272,592],"parameters":{"jsCode":"const config = $('Gateway Config').first().json;\nconst output = ($input.first().json.stdout || '').trim();\nconst body = output || 'No pipeline data available.';\nconst matrixBody = JSON.stringify({ msgtype: 'm.notice', body });\nreturn [{ json: { matrixBody, ...config } }];"},"typeVersion":2},{"id":"handle-help","name":"Handle Help","type":"n8n-nodes-base.code","position":[2064,832],"parameters":{"jsCode":"const config = $('Gateway Config').first().json;\nconst body = 'Claude Gateway Commands:\\n\\n'\n  + '!session current  — Show current session\\n'\n  + '!session list     — List all sessions\\n'\n  + '!session done     — End current session\\n'\n  + '!session cancel   — Cancel session (no summary)\\n'\n  + '!session pause    — Pause message delivery\\n'\n  + '!session resume   — Resume paused session\\n\\n'\n  + '!issue status     — In-progress issues\\n'\n  + '!issue info <id>  — Issue details\\n\\n'\n  + '!pipeline status  — Pipeline overview\\n\\n'\n  + '!system status    — Server load & memory\\n\\n'\n  + 'Prefix routing: PROJ-4: your message';\nconst matrixBody = JSON.stringify({ msgtype: 'm.notice', body });\nreturn [{ json: { matrixBody, ...config } }];"},"typeVersion":2},{"id":"handle-system","name":"Handle System Command","type":"n8n-nodes-base.ssh","position":[2064,976],"parameters":{"command":"LOAD=$(uptime | sed 's/.*load average: //') && MEM=$(free -h | awk '/Mem:/{print $3\"/\"$2}') && PROCS=$(pgrep -c -f claude 2>/dev/null || echo 0) && echo \"Load: $LOAD\" && echo \"Memory: $MEM\" && echo \"Claude processes: $PROCS\"","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"SSH_CRED_ID","name":"Server SSH Key"}},"typeVersion":1,"continueOnFail":true},{"id":"format-system","name":"Format System Response","type":"n8n-nodes-base.code","position":[2272,912],"parameters":{"jsCode":"const config = $('Gateway Config').first().json;\nconst output = ($input.first().json.stdout || '').trim();\nconst body = output || 'No system data.';\nconst matrixBody = JSON.stringify({ msgtype: 'm.notice', body });\nreturn [{ json: { matrixBody, ...config } }];"},"typeVersion":2},{"id":"handle-unknown","name":"Handle Unknown Command","type":"n8n-nodes-base.code","position":[2064,1168],"parameters":{"jsCode":"const config = $('Gateway Config').first().json;\nif ($json.matrixBody) return [{ json: { matrixBody: $json.matrixBody, ...config } }];\nconst body = 'Unknown command: !' + ($json.command || 'unknown') + '\\nType !help to see available commands.';\nconst matrixBody = JSON.stringify({ msgtype: 'm.notice', body });\nreturn [{ json: { matrixBody, ...config } }];"},"typeVersion":2},{"id":"post-cmd-response","name":"Post Command Response","type":"n8n-nodes-base.httpRequest","position":[2496,592],"parameters":{"url":"={{ $json.MATRIX_HOMESERVER }}/_matrix/client/v3/rooms/{{ $json.MATRIX_ROOM_ID }}/send/m.room.message/cmd-{{ Date.now() }}","method":"PUT","options":{},"jsonBody":"={{ $json.matrixBody }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"credentials":{"httpHeaderAuth":{"id":"MATRIX_CRED_ID","name":"Matrix Bot Token"}},"typeVersion":4.2,"continueOnFail":true},{"id":"session-end-check","name":"Is Session Done?","type":"n8n-nodes-base.if","position":[2720,592],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"c70feebe","operator":{"type":"string","operation":"contains"},"leftValue":"={{ $('Handle Session Command').first().json.stdout || '' }}","rightValue":"session_id"}]}},"typeVersion":2},{"id":"cleanup-session","name":"Clean Up & End Session","type":"n8n-nodes-base.ssh","position":[2944,592],"parameters":{"command":"=DB=\"{{ $(\"Gateway Config\").first().json.DB_PATH }}\"\nGW=\"{{ $(\"Gateway Config\").first().json.CONTEXT_DIR }}\"\nPROJECT=\"{{ $(\"Gateway Config\").first().json.CLAUDE_PROJECT_PATH }}\"\nCLAUDE_BIN=\"{{ $(\"Gateway Config\").first().json.CLAUDE_BINARY }}\"\nSESSION_OUT=\"{{ $(\"Handle Session Command\").first().json.stdout || '' }}\"\n\nSESSION_OUT=$(echo \"$SESSION_OUT\" | tr -d '\\n')\n[ -z \"$SESSION_OUT\" ] && echo \"NO_SESSION\" && exit 0\n\nSID=$(echo \"$SESSION_OUT\" | python3 -c \"import sys,json; print(json.load(sys.stdin).get('session_id',''))\" 2>/dev/null)\nISSUE=$(echo \"$SESSION_OUT\" | python3 -c \"import sys,json; print(json.load(sys.stdin).get('issue_id',''))\" 2>/dev/null)\n[ -z \"$SID\" ] && echo \"NO_SESSION\" && exit 0\n\n# Get summary from Claude\nunset CLAUDECODE\ncd \"$PROJECT\"\ntimeout 60 \"$CLAUDE_BIN\" -r \"$SID\" -p \"Summarize what you worked on in 2-3 sentences\" --output-format json --dangerously-skip-permissions 2>&1\n\n# Archive and clean up\nsqlite3 \"$DB\" \"INSERT INTO session_log (issue_id,issue_title,session_id,started_at,message_count,outcome) SELECT issue_id,issue_title,session_id,started_at,message_count,'done' FROM sessions WHERE issue_id='$ISSUE'; DELETE FROM sessions WHERE issue_id='$ISSUE'; DELETE FROM queue WHERE issue_id='$ISSUE';\"\nrm -f \"$GW/gateway.lock\"\ntouch \"$GW/gateway.cooldown.$ISSUE\"\necho \"SESSION_ENDED:$ISSUE\"","authentication":"privateKey"},"credentials":{"sshPrivateKey":{"id":"SSH_CRED_ID","name":"Server SSH Key"}},"typeVersion":1,"continueOnFail":true},{"id":"post-session-ended","name":"Post Session Ended","type":"n8n-nodes-base.httpRequest","position":[3152,592],"parameters":{"url":"={{ $('Gateway Config').first().json.MATRIX_HOMESERVER }}/_matrix/client/v3/rooms/{{ $('Gateway Config').first().json.MATRIX_ROOM_ID }}/send/m.room.message/end-{{ Date.now() }}","method":"PUT","options":{},"jsonBody":"={{ JSON.stringify({ msgtype: 'm.notice', body: 'Session ended. Summary posted to YouTrack.' }) }}","sendBody":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"credentials":{"httpHeaderAuth":{"id":"MATRIX_CRED_ID","name":"Matrix Bot Token"}},"typeVersion":4.2,"continueOnFail":true},{"id":"cred-sticky","name":"Sticky Note Credentials","type":"n8n-nodes-base.stickyNote","position":[208,800],"parameters":{"color":6,"width":520,"height":508,"content":"### Credentials Setup\n\n**Do NOT store API tokens in workflow nodes.**\nUse n8n's credential system instead:\n\n1. **Matrix Bot Token** — HTTP Header Auth\n   `Authorization: Bearer <token>`\n\n2. **SSH Private Key** — SSH credential\n   for your Claude Code server\n\n**API tokens for YouTrack & GitLab:**\nSSH command nodes reference `$YT_TOKEN` and\n`$GL_TOKEN` environment variables. Set these\nin `~/.bashrc` on the remote server:\n```\nexport YT_TOKEN=perm-YOUR-TOKEN\nexport GL_TOKEN=glpat-YOUR-TOKEN\n```\n\nThis keeps tokens out of the workflow JSON\nand uses the server's environment instead."},"typeVersion":1}],"active":false,"pinData":{},"settings":{"callerPolicy":"workflowsFromSameOwner","availableInMCP":false,"executionOrder":"v1","saveDataErrorExecution":"all","saveDataSuccessExecution":"none"},"versionId":"929c4f9a-8b21-4f30-97d1-7b2f6fc1fdd4","connections":{"Check Lock":{"main":[[{"node":"Is Locked?","type":"main","index":0}]]},"Is Locked?":{"main":[[{"node":"Post Busy Notice","type":"main","index":0}],[{"node":"Read Session & Acquire Lock","type":"main","index":0}]]},"Handle Help":{"main":[[{"node":"Post Command Response","type":"main","index":0}]]},"Has Messages?":{"main":[[{"node":"Detect Command","type":"main","index":0}]]},"Command Router":{"main":[[{"node":"Check Lock","type":"main","index":0}],[{"node":"Handle Session Command","type":"main","index":0}],[{"node":"Handle Help","type":"main","index":0}],[{"node":"Handle Issue Command","type":"main","index":0}],[{"node":"Handle Pipeline Command","type":"main","index":0}],[{"node":"Handle System Command","type":"main","index":0}],[{"node":"Handle Unknown Command","type":"main","index":0}]]},"Detect Command":{"main":[[{"node":"Command Router","type":"main","index":0}]]},"Get Sync Token":{"main":[[{"node":"Poll Matrix Sync","type":"main","index":0}]]},"Poll Every 30s":{"main":[[{"node":"Get Sync Token","type":"main","index":0}]]},"Extract Messages":{"main":[[{"node":"Has Messages?","type":"main","index":0}]]},"Is Session Done?":{"main":[[{"node":"Clean Up & End Session","type":"main","index":0}]]},"Poll Matrix Sync":{"main":[[{"node":"Extract Messages","type":"main","index":0}]]},"Handle Issue Command":{"main":[[{"node":"Format Issue Response","type":"main","index":0}]]},"Format Issue Response":{"main":[[{"node":"Post Command Response","type":"main","index":0}]]},"Handle System Command":{"main":[[{"node":"Format System Response","type":"main","index":0}]]},"Parse Claude Response":{"main":[[{"node":"Post Response to Matrix","type":"main","index":0}]]},"Post Command Response":{"main":[[{"node":"Is Session Done?","type":"main","index":0}]]},"Resume Claude Session":{"main":[[{"node":"Parse Claude Response","type":"main","index":0}]]},"Clean Up & End Session":{"main":[[{"node":"Post Session Ended","type":"main","index":0}]]},"Format System Response":{"main":[[{"node":"Post Command Response","type":"main","index":0}]]},"Handle Session Command":{"main":[[{"node":"Format Session Response","type":"main","index":0}]]},"Handle Unknown Command":{"main":[[{"node":"Post Command Response","type":"main","index":0}]]},"Format Session Response":{"main":[[{"node":"Post Command Response","type":"main","index":0}]]},"Handle Pipeline Command":{"main":[[{"node":"Format Pipeline Response","type":"main","index":0}]]},"Post Response to Matrix":{"main":[[{"node":"Release Lock","type":"main","index":0}]]},"Format Pipeline Response":{"main":[[{"node":"Post Command Response","type":"main","index":0}]]},"Read Session & Acquire Lock":{"main":[[{"node":"Resume Claude Session","type":"main","index":0}]]}}},"lastUpdatedBy":1,"workflowInfo":{"nodeCount":38,"nodeTypes":{"n8n-nodes-base.if":{"count":3},"n8n-nodes-base.set":{"count":1},"n8n-nodes-base.ssh":{"count":9},"n8n-nodes-base.code":{"count":10},"n8n-nodes-base.switch":{"count":1},"n8n-nodes-base.stickyNote":{"count":8},"n8n-nodes-base.httpRequest":{"count":5},"n8n-nodes-base.scheduleTrigger":{"count":1}}},"status":"published","readyToDemo":null,"user":{"name":"Kyriakos Papadopoulos","username":"lzrd","bio":"Senior IT Consultant | Infrastructure, Security & Network Integration","verified":false,"links":["https://www.linkedin.com/in/kyriakospapadopoulos/"],"avatar":"https://gravatar.com/avatar/0157f0ca54e7e616ada7192575c8db95117cea2b948cff7afc5a6a1f2c406c14?r=pg&d=retro&size=200"},"nodes":[{"id":19,"icon":"file:httprequest.svg","name":"n8n-nodes-base.httpRequest","codex":{"data":{"alias":["API","Request","URL","Build","cURL"],"resources":{"generic":[{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/learn-how-to-automatically-cross-post-your-content-with-n8n/","icon":"✍️","label":"Learn how to automatically cross-post your content with n8n"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/","icon":"🛳","label":"Running n8n on ships: An interview with Maranics"},{"url":"https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/","icon":" 🪢","label":"What are APIs and how to use them with no code"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/world-poetry-day-workflow/","icon":"📜","label":"Celebrating World Poetry Day with a daily poem in Telegram"},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/automate-designs-with-bannerbear-and-n8n/","icon":"🎨","label":"Automate Designs with Bannerbear and n8n"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/","icon":"📱","label":"Building an expense tracking app in 10 minutes"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/how-to-use-the-http-request-node-the-swiss-army-knife-for-workflow-automation/","icon":"🧰","label":"How to use the HTTP Request Node - The Swiss Army Knife for Workflow Automation"},{"url":"https://n8n.io/blog/learn-how-to-use-webhooks-with-mattermost-slash-commands/","icon":"🦄","label":"Learn how to use webhooks with Mattermost slash commands"},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/","icon":"📈","label":"A low-code bitcoin ticker built with QuestDB and n8n.io"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/automations-for-activists/","icon":"✨","label":"How Common Knowledge use workflow automation for activism"},{"url":"https://n8n.io/blog/creating-scheduled-text-affirmations-with-n8n/","icon":"🤟","label":"Creating scheduled text affirmations with n8n"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"output\"]","defaults":{"name":"HTTP Request","color":"#0004F5"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00MCAyMEM0MCA4Ljk1MzE0IDMxLjA0NjkgMCAyMCAwQzguOTUzMTQgMCAwIDguOTUzMTQgMCAyMEMwIDMxLjA0NjkgOC45NTMxNCA0MCAyMCA0MEMzMS4wNDY5IDQwIDQwIDMxLjA0NjkgNDAgMjBaTTIwIDM2Ljk0NThDMTguODg1MiAzNi45NDU4IDE3LjEzNzggMzUuOTY3IDE1LjQ5OTggMzIuNjk4NUMxNC43OTY0IDMxLjI5MTggMTQuMTk2MSAyOS41NDMxIDEzLjc1MjYgMjcuNjg0N0gyNi4xODk4QzI1LjgwNDUgMjkuNTQwMyAyNS4yMDQ0IDMxLjI5MDEgMjQuNTAwMiAzMi42OTg1QzIyLjg2MjIgMzUuOTY3IDIxLjExNDggMzYuOTQ1OCAyMCAzNi45NDU4Wk0xMi45MDY0IDIwQzEyLjkwNjQgMjEuNjA5NyAxMy4wMDg3IDIzLjE2NCAxMy4yMDAzIDI0LjYzMDVIMjYuNzk5N0MyNi45OTEzIDIzLjE2NCAyNy4wOTM2IDIxLjYwOTcgMjcuMDkzNiAyMEMyNy4wOTM2IDE4LjM5MDMgMjYuOTkxMyAxNi44MzYgMjYuNzk5NyAxNS4zNjk1SDEzLjIwMDNDMTMuMDA4NyAxNi44MzYgMTIuOTA2NCAxOC4zOTAzIDEyLjkwNjQgMjBaTTIwIDMuMDU0MTlDMjEuMTE0OSAzLjA1NDE5IDIyLjg2MjIgNC4wMzA3OCAyNC41MDAxIDcuMzAwMzlDMjUuMjA2NiA4LjcxNDA4IDI1LjgwNzIgMTAuNDA2NyAyNi4xOTIgMTIuMzE1M0gxMy43NTAxQzE0LjE5MzMgMTAuNDA0NyAxNC43OTQyIDguNzEyNTQgMTUuNDk5OCA3LjMwMDY0QzE3LjEzNzcgNC4wMzA4MyAxOC44ODUxIDMuMDU0MTkgMjAgMy4wNTQxOVpNMzAuMTQ3OCAyMEMzMC4xNDc4IDE4LjQwOTkgMzAuMDU0MyAxNi44NjE3IDI5LjgyMjcgMTUuMzY5NUgzNi4zMDQyQzM2LjcyNTIgMTYuODQyIDM2Ljk0NTggMTguMzk2NCAzNi45NDU4IDIwQzM2Ljk0NTggMjEuNjAzNiAzNi43MjUyIDIzLjE1OCAzNi4zMDQyIDI0LjYzMDVIMjkuODIyN0MzMC4wNTQzIDIzLjEzODMgMzAuMTQ3OCAyMS41OTAxIDMwLjE0NzggMjBaTTI2LjI3NjcgNC4yNTUxMkMyNy42MzY1IDYuMzYwMTkgMjguNzExIDkuMTMyIDI5LjM3NzQgMTIuMzE1M0gzNS4xMDQ2QzMzLjI1MTEgOC42NjggMzAuMTA3IDUuNzgzNDYgMjYuMjc2NyA0LjI1NTEyWk0xMC42MjI2IDEyLjMxNTNINC44OTI5M0M2Ljc1MTQ3IDguNjY3ODQgOS44OTM1MSA1Ljc4MzQxIDEzLjcyMzIgNC4yNTUxM0MxMi4zNjM1IDYuMzYwMjEgMTEuMjg5IDkuMTMyMDEgMTAuNjIyNiAxMi4zMTUzWk0zLjA1NDE5IDIwQzMuMDU0MTkgMjEuNjAzIDMuMjc3NDMgMjMuMTU3NSAzLjY5NDg0IDI0LjYzMDVIMTAuMTIxN0M5Ljk0NjE5IDIzLjE0MiA5Ljg1MjIyIDIxLjU5NDMgOS44NTIyMiAyMEM5Ljg1MjIyIDE4LjQwNTcgOS45NDYxOSAxNi44NTggMTAuMTIxNyAxNS4zNjk1SDMuNjk0ODRDMy4yNzc0MyAxNi44NDI1IDMuMDU0MTkgMTguMzk3IDMuMDU0MTkgMjBaTTI2LjI3NjYgMzUuNzQyN0MyNy42MzY1IDMzLjYzOTMgMjguNzExIDMwLjg2OCAyOS4zNzc0IDI3LjY4NDdIMzUuMTA0NkMzMy4yNTEgMzEuMzMyMiAzMC4xMDY4IDM0LjIxNzkgMjYuMjc2NiAzNS43NDI3Wk0xMy43MjM0IDM1Ljc0MjdDOS44OTM2OSAzNC4yMTc5IDYuNzUxNTUgMzEuMzMyNCA0Ljg5MjkzIDI3LjY4NDdIMTAuNjIyNkMxMS4yODkgMzAuODY4IDEyLjM2MzUgMzMuNjM5MyAxMy43MjM0IDM1Ljc0MjdaIiBmaWxsPSIjM0E0MkU5Ii8+Cjwvc3ZnPgo="},"displayName":"HTTP Request","typeVersion":4,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","codex":{"data":{"alias":["Router","Filter","Condition","Logic","Boolean","Branch"],"details":"The IF node can be used to implement binary conditional logic in your workflow. You can set up one-to-many conditions to evaluate each item of data being inputted into the node. That data will either evaluate to TRUE or FALSE and route out of the node accordingly.\n\nThis node has multiple types of conditions: Bool, String, Number, and Date & Time.","resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/create-a-toxic-language-detector-for-telegram/","icon":"🤬","label":"Create a toxic language detector for Telegram in 4 step"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/automation-for-maintainers-of-open-source-projects/","icon":"🏷️","label":"How to automatically manage contributions to open-source projects"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/","icon":"🙌","label":"Sending Automated Congratulations with Google Sheets, Twilio, and n8n "},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.if/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Flow"]}}},"group":"[\"transform\"]","defaults":{"name":"If","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"displayName":"If","typeVersion":2,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":38,"icon":"fa:pen","name":"n8n-nodes-base.set","codex":{"data":{"alias":["Set","JS","JSON","Filter","Transform","Map"],"resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/database-monitoring-and-alerting-with-n8n/","icon":"📡","label":"Database Monitoring and Alerting with n8n"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/","icon":"📱","label":"Building an expense tracking app in 10 minutes"},{"url":"https://n8n.io/blog/the-ultimate-guide-to-automate-your-video-collaboration-with-whereby-mattermost-and-n8n/","icon":"📹","label":"The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/","icon":"🧰","label":"Learn to Build Powerful API Endpoints Using Webhooks"},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/","icon":"📈","label":"A low-code bitcoin ticker built with QuestDB and n8n.io"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Data Transformation"]}}},"group":"[\"input\"]","defaults":{"name":"Edit Fields"},"iconData":{"icon":"pen","type":"icon"},"displayName":"Edit Fields (Set)","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":112,"icon":"fa:map-signs","name":"n8n-nodes-base.switch","codex":{"data":{"alias":["Router","If","Path","Filter","Condition","Logic","Branch","Case"],"resources":{"generic":[{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/build-your-own-virtual-assistant-with-n8n-a-step-by-step-guide/","icon":"👦","label":"Build your own virtual assistant with n8n: A step by step guide"},{"url":"https://n8n.io/blog/automation-for-maintainers-of-open-source-projects/","icon":"🏷️","label":"How to automatically manage contributions to open-source projects"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.switch/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Flow"]}}},"group":"[\"transform\"]","defaults":{"name":"Switch","color":"#506000"},"iconData":{"icon":"map-signs","type":"icon"},"displayName":"Switch","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":490,"icon":"fa:terminal","name":"n8n-nodes-base.ssh","codex":{"data":{"alias":["remote"],"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.ssh/"}]},"categories":["Core Nodes","Development"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":["Helpers"]}},"group":"[\"input\"]","defaults":{"name":"SSH","color":"#000000"},"iconData":{"icon":"terminal","type":"icon"},"displayName":"SSH","typeVersion":1,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":565,"icon":"fa:sticky-note","name":"n8n-nodes-base.stickyNote","codex":{"data":{"alias":["Comments","Notes","Sticky"],"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"input\"]","defaults":{"name":"Sticky Note","color":"#FFD233"},"iconData":{"icon":"sticky-note","type":"icon"},"displayName":"Sticky Note","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":834,"icon":"file:code.svg","name":"n8n-nodes-base.code","codex":{"data":{"alias":["cpde","Javascript","JS","Python","Script","Custom Code","Function"],"details":"The Code node allows you to execute JavaScript in your workflow.","resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers","Data Transformation"]}}},"group":"[\"transform\"]","defaults":{"name":"Code"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTcxXzQ0MSkiPgo8cGF0aCBkPSJNMTcwLjI4MyA0OEgxOTYuNUMyMDMuMTI3IDQ4IDIwOC41IDQyLjYyNzQgMjA4LjUgMzZWMTJDMjA4LjUgNS4zNzI1OCAyMDMuMTI3IDAgMTk2LjUgMEgxNzAuMjgzQzEyNi4xIDAgOTAuMjgzIDM1LjgxNzIgOTAuMjgzIDgwVjE3NkM5MC4yODMgMjA2LjkyOCA2NS4yMTA5IDIzMiAzNC4yODMgMjMySDIzQzE2LjM3MjYgMjMyIDExIDIzNy4zNzIgMTEgMjQ0VjI2OEMxMSAyNzQuNjI3IDE2LjM3MjQgMjgwIDIyLjk5OTYgMjgwTDM0LjI4MyAyODBDNjUuMjEwOSAyODAgOTAuMjgzIDMwNS4wNzIgOTAuMjgzIDMzNlY0NDBDOTAuMjgzIDQ3OS43NjQgMTIyLjUxOCA1MTIgMTYyLjI4MyA1MTJIMTk2LjVDMjAzLjEyNyA1MTIgMjA4LjUgNTA2LjYyNyAyMDguNSA1MDBWNDc2QzIwOC41IDQ2OS4zNzMgMjAzLjEyNyA0NjQgMTk2LjUgNDY0SDE2Mi4yODNDMTQ5LjAyOCA0NjQgMTM4LjI4MyA0NTMuMjU1IDEzOC4yODMgNDQwVjMzNkMxMzguMjgzIDMwOS4wMjIgMTI4LjAxMSAyODQuNDQzIDExMS4xNjQgMjY1Ljk2MUMxMDYuMTA5IDI2MC40MTYgMTA2LjEwOSAyNTEuNTg0IDExMS4xNjQgMjQ2LjAzOUMxMjguMDExIDIyNy41NTcgMTM4LjI4MyAyMDIuOTc4IDEzOC4yODMgMTc2VjgwQzEzOC4yODMgNjIuMzI2OSAxNTIuNjEgNDggMTcwLjI4MyA0OFoiIGZpbGw9IiNGRjk5MjIiLz4KPHBhdGggZD0iTTMwNSAzNkMzMDUgNDIuNjI3NCAzMTAuMzczIDQ4IDMxNyA0OEgzNDIuOTc5QzM2MC42NTIgNDggMzc0Ljk3OCA2Mi4zMjY5IDM3NC45NzggODBWMTc2QzM3NC45NzggMjAyLjk3OCAzODUuMjUxIDIyNy41NTcgNDAyLjA5OCAyNDYuMDM5QzQwNy4xNTMgMjUxLjU4NCA0MDcuMTUzIDI2MC40MTYgNDAyLjA5OCAyNjUuOTYxQzM4NS4yNTEgMjg0LjQ0MyAzNzQuOTc4IDMwOS4wMjIgMzc0Ljk3OCAzMzZWNDMyQzM3NC45NzggNDQ5LjY3MyAzNjAuNjUyIDQ2NCAzNDIuOTc5IDQ2NEgzMTdDMzEwLjM3MyA0NjQgMzA1IDQ2OS4zNzMgMzA1IDQ3NlY1MDBDMzA1IDUwNi42MjcgMzEwLjM3MyA1MTIgMzE3IDUxMkgzNDIuOTc5QzM4Ny4xNjEgNTEyIDQyMi45NzggNDc2LjE4MyA0MjIuOTc4IDQzMlYzMzZDNDIyLjk3OCAzMDUuMDcyIDQ0OC4wNTEgMjgwIDQ3OC45NzkgMjgwSDQ5MEM0OTYuNjI3IDI4MCA1MDIgMjc0LjYyOCA1MDIgMjY4VjI0NEM1MDIgMjM3LjM3MyA0OTYuNjI4IDIzMiA0OTAgMjMyTDQ3OC45NzkgMjMyQzQ0OC4wNTEgMjMyIDQyMi45NzggMjA2LjkyOCA0MjIuOTc4IDE3NlY4MEM0MjIuOTc4IDM1LjgxNzIgMzg3LjE2MSAwIDM0Mi45NzkgMEgzMTdDMzEwLjM3MyAwIDMwNSA1LjM3MjU4IDMwNSAxMlYzNloiIGZpbGw9IiNGRjk5MjIiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF8xMTcxXzQ0MSI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="},"displayName":"Code","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":839,"icon":"fa:clock","name":"n8n-nodes-base.scheduleTrigger","codex":{"data":{"alias":["Time","Scheduler","Polling","Cron","Interval"],"resources":{"generic":[],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.scheduletrigger/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"trigger\",\"schedule\"]","defaults":{"name":"Schedule Trigger","color":"#31C49F"},"iconData":{"icon":"clock","type":"icon"},"displayName":"Schedule Trigger","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]}],"categories":[{"id":16,"name":"DevOps"},{"id":47,"name":"AI Chatbot"}],"image":[{"id":4905,"url":"https://n8niostorageaccount.blob.core.windows.net/n8nio-strapi-blobs-prod/assets/Screenshot_from_2026_03_12_18_32_01_f9864fef98.png"}]}}