N8n Setup

n8n Setup and Configuration #

This document explains how n8n is configured to orchestrate the Tofie workflow automation.

Overview #

n8n serves as the central automation hub, receiving webhooks from Linear and executing Tofie scripts on the Coder instance via SSH.

Environment: bonsai.app.n8n.cloud (cloud-hosted)

Workflow Structure #

Main Tofie Workflow #

Webhook URL: https://bonsai.app.n8n.cloud/webhook/tofie-event Method: POST Authentication: Webhook signature validation

Nodes:

  1. Webhook Trigger

    • Receives Linear webhook events
    • Returns immediate 200 OK response
  2. Parse Linear Event

    • Extracts event type (Comment.created, Issue.updated, etc.)
    • Parses comment body for Tofie mentions
  3. Command Router (Switch node)

    • Routes to appropriate workflow based on command:
      • plan → Planning workflow
      • implement → Implementation workflow
      • review → Review workflow
      • pr → PR submission workflow
  4. Extract Issue Context

    • Pulls full issue details from Linear API
    • Gets all comments on the issue
    • Extracts project information
  5. Update Linear Status (HTTP Request)

    • Changes issue status (e.g., Todo → Planning)
    • Adds comment confirming work started
  6. Build Script Input (Function node)

    const branchName = `${user}/${issueKey}-${slugify(issueTitle)}`;
    
    return {
      branchName,
      issueIdentifier: items[0].json.issue.identifier,
      agentSessionId: $execution.id,
      project: items[0].json.issue.project,
      issue: items[0].json.issue
    };
    
  7. SSH Execute (SSH node)

    • Connects to Coder instance
    • Executes appropriate Tofie script
    • Pipes JSON input via stdin
    • Captures stdout/stderr
  8. Parse Script Output (Function node)

    const result = JSON.parse(items[0].json.stdout);
    
    if (!result.success) {
      throw new Error(result.error || 'Script execution failed');
    }
    
    return result;
    
  9. Update Linear with Result (HTTP Request)

    • Posts plan/implementation summary as comment
    • Updates issue status (e.g., Planning → Planned)
    • Adds PR link if created
  10. Error Handler (Error Workflow)

    • Catches any failures
    • Posts error message to Linear
    • Logs to n8n error log

Configuration #

Credentials #

Linear API:

  • Type: HTTP Header Auth
  • Header Name: Authorization
  • Value: Bearer <LINEAR_API_KEY>

SSH Connection:

  • Type: SSH
  • Host: coder-instance.internal
  • Port: 22
  • Username: coder
  • Private Key: <SSH_PRIVATE_KEY>

GitHub (for PR operations):

  • Stored in Doppler on Coder instance
  • Retrieved by scripts via doppler secrets get GITHUB_TOKEN

Environment Variables #

Set in n8n workflow settings:

LINEAR_WORKSPACE_ID=<workspace-id>
LINEAR_WEBHOOK_SECRET=<webhook-secret>
CODER_HOST=coder-instance.internal
TOFIE_SCRIPTS_PATH=/home/coder/bonsai/tools/local/scripts/tofie

Webhook Signature Validation #

Purpose: Verify webhooks are from Linear, not malicious actors

Implementation:

const crypto = require('crypto');

const signature = $node["Webhook"].json.headers['linear-signature'];
const payload = JSON.stringify($node["Webhook"].json.body);
const secret = $env.LINEAR_WEBHOOK_SECRET;

const expectedSignature = crypto
  .createHmac('sha256', secret)
  .update(payload)
  .digest('hex');

if (signature !== expectedSignature) {
  throw new Error('Invalid webhook signature');
}

return items;

Command Parsing #

Function node logic:

const comment = items[0].json.data.body.toLowerCase();

// Check for Tofie mention
if (!comment.includes('@tofie')) {
  return [];  // Skip, not a Tofie command
}

// Extract command
let command = 'unknown';
if (comment.includes('plan')) command = 'plan';
else if (comment.includes('implement')) command = 'implement';
else if (comment.includes('review')) command = 'review';
else if (comment.includes('pr')) command = 'pr';

// Check thoroughness for planning
let thoroughness = 'medium';
if (comment.includes('thorough') || comment.includes('deep')) {
  thoroughness = 'very thorough';
}

return [{
  json: {
    command,
    thoroughness,
    ...items[0].json
  }
}];

Script Execution #

SSH Command Template:

cd {{$env.TOFIE_SCRIPTS_PATH}} && \
  ./{{$node["Command Router"].json.command}}.sh

stdin: JSON from “Build Script Input” node stdout: Captured and parsed as JSON stderr: Logged for debugging

Timeout: 30 minutes (configurable per command)

Error Handling #

Retry Logic #

Configured retries:

  • Webhook processing: No retry (immediate response required)
  • SSH execution: 3 retries with exponential backoff
    • Retry 1: After 30 seconds
    • Retry 2: After 2 minutes
    • Retry 3: After 5 minutes
  • Linear API calls: 5 retries with backoff

Error Workflow #

Separate workflow triggered on any error:

  1. Catch Error

    • Receives error details and context
  2. Format Error Message

    ❌ Tofie Error
    
    **Command**: {{$json.command}}
    **Error**: {{$json.error}}
    
    Please check logs or contact DevOps if this persists.
    
  3. Post to Linear

    • Adds error comment to issue
    • Optionally reverts status change
  4. Log to Monitoring

    • Send to logging service (if configured)
    • Track error metrics

Status Transitions #

Configured transitions:

Command Initial Status During Execution Success Status Error Status
plan Todo, Backlog Planning Planned Todo
implement Planned In Progress In Review Planned
review In Review Under Review Reviewed In Review
pr In Review, Reviewed PR Creating PR Created In Review

Linear API Integration #

GraphQL Queries #

Get Issue Details:

query GetIssue($id: String!) {
  issue(id: $id) {
    id
    identifier
    title
    description
    state { id name }
    project { id name key }
    comments { nodes { id body createdAt user { name } } }
    labels { nodes { id name } }
  }
}

Update Issue Status:

mutation UpdateIssue($id: String!, $stateId: String!) {
  issueUpdate(id: $id, input: { stateId: $stateId }) {
    success
    issue { id state { name } }
  }
}

Create Comment:

mutation CreateComment($issueId: String!, $body: String!) {
  commentCreate(input: { issueId: $issueId, body: $body }) {
    success
    comment { id }
  }
}

Monitoring #

Workflow Execution Tracking #

n8n automatically tracks:

  • Execution count
  • Success/failure rate
  • Average execution time
  • Active executions

Access: n8n UI → Executions tab

Custom Metrics #

Tracked via function nodes:

  • Command type distribution
  • Planning vs subagent usage
  • PR creation success rate
  • Average time by workflow stage

Debugging #

Execution Logs #

View in n8n UI:

  1. Go to Executions tab
  2. Click on execution ID
  3. View node-by-node execution data

Each node shows:

  • Input data
  • Output data
  • Execution time
  • Error details (if any)

Test Webhooks #

Manual testing:

  1. Go to Webhook node in workflow
  2. Click “Listen for Test Event”
  3. Trigger event from Linear (or use curl)
  4. View test execution results

curl example:

curl -X POST https://bonsai.app.n8n.cloud/webhook/tofie-event \
  -H "Content-Type: application/json" \
  -H "linear-signature: <signature>" \
  -d '{ "action": "create", "type": "Comment", ... }'

SSH Debugging #

Test SSH connection:

ssh -i ~/.ssh/tofie_key coder@coder-instance.internal \
  "echo 'SSH connection successful'"

Test script execution:

echo '{"branchName": "test", ...}' | \
  ssh coder@coder-instance.internal \
    "cd /home/coder/bonsai/tools/local/scripts/tofie && ./planning.sh"

Maintenance #

Regular Tasks #

Weekly:

  • Review failed executions
  • Check error rate trends
  • Validate webhook signature

Monthly:

  • Update credentials if rotated
  • Review and optimize workflows
  • Clean up old execution logs

Workflow Updates #

Best practices:

  1. Test changes in n8n’s “Test” environment first
  2. Export workflow before making changes (backup)
  3. Use version control for workflow JSON
  4. Document changes in workflow description

Export workflow:

# In n8n UI: Workflow → Settings → Download
# Or use n8n API:
curl -X GET https://bonsai.app.n8n.cloud/api/v1/workflows/123 \
  -H "X-N8N-API-KEY: <api-key>"

Security #

Webhook Security #

  • ✅ Signature validation enabled
  • ✅ HTTPS only
  • ✅ No sensitive data in webhooks (IDs only, fetch full data via API)

SSH Security #

  • ✅ Key-based authentication (no passwords)
  • ✅ Limited user permissions on Coder instance
  • ✅ Keys rotated every 90 days
  • ✅ Connection from n8n cloud IP only (firewall rule)

Credential Management #

  • ✅ n8n credential vault (encrypted at rest)
  • ✅ Doppler for GitHub tokens
  • ✅ No credentials in workflow JSON
  • ✅ Principle of least privilege

Troubleshooting #

See Troubleshooting Guide for common issues and solutions.