mvp-factory-openhands/N8N_DATA_PRESERVATION_SOLUT...

7.5 KiB

n8n OpenHands Integration - Data Preservation Solution

Problem Statement

When using the SSH node in n8n to execute the OpenHands SDK wrapper, all input data is lost. The SSH node completely overwrites the workflow data with only:

{
  "code": 0,
  "signal": null,
  "stdout": "...",
  "stderr": "..."
}

This means repository information (name, branch, commit) extracted earlier in the workflow is lost by the time we reach the response formatting node.

The Solution: Spread Operator

In the "Check Build Status" node (the node after SSH), use the spread operator (...) to preserve all incoming data:

// FIXED: Preserve incoming data instead of creating new item
// Get the incoming data (has repo_name, repo_full_name, etc.)
const item = $json;

// Add status but keep ALL the repo data
return {
  ...item,  // Spread operator keeps all existing fields
  status: 'SUCCESS',
  message: 'Build completed successfully',
  timestamp: new Date().toISOString()
};

Why This Works

  1. SSH Node Input: {repo_name: "test-repo", repo_full_name: "bam/test-project", ...}
  2. SSH Node Output: {code: 0, stdout: "...", stderr: "..."} ← REPO DATA LOST
  3. Check Build Status Input: {code: 0, stdout: "..."} ← Only SSH output
  4. Check Build Status Output: {...item, status: "SUCCESS", ...} ← Preserves SSH output PLUS adds status
  5. Format Response Input: Has both SSH output AND the status field

Wait, this still doesn't preserve the original repo data!

ACTUAL Solution: Pass Through Option

The SSH node has a passThrough option that preserves input data. Enable it in the SSH node configuration:

{
  "parameters": {
    "command": "sh /home/bam/claude/mvp-factory/openhands-sdk-wrapper-sh.sh ...",
    "sessionId": "enhanced-session",
    "authentication": "privateKey",
    "options": {
      "passThrough": true  // ← ADD THIS
    }
  }
}

With passThrough: true, the SSH node preserves all input AND adds its output:

{
  "repo_name": "test-repo",
  "repo_full_name": "bam/test-project",
  "code": 0,
  "stdout": "...",
  "stderr": "..."
}

Then the Check Build Status node just adds fields without losing anything.

Working Workflow Configuration

Node 1: Webhook

{
  "parameters": {
    "httpMethod": "POST",
    "path": "openhands-working",
    "options": {}
  }
}

Node 2: Extract Repo Info

// CORRECT: Data is in $json.body
const payload = $json.body;

const repoName = payload.repository?.name || 'unknown';
const repoFullName = payload.repository?.full_name || 'unknown';
const repoCloneUrl = payload.repository?.clone_url || '';
const branch = payload.ref?.replace('refs/heads/', '') || 'main';
const commitSha = payload.after || '';
const commitMessage = payload.commits?.[0]?.message || 'No message';
const pusher = payload.pusher?.username || 'unknown';

const task = 'Build and test project ' + repoFullName + ' on branch ' + branch + '. ' +
             'Latest commit: "' + commitMessage + '". ' +
             'Clone the repository from ' + repoCloneUrl + ' and run: npm install && npm test && npm build. ' +
             'Report any errors found.';

return {
  repo_name: repoName,
  repo_full_name: repoFullName,
  repo_clone_url: repoCloneUrl,
  branch: branch,
  commit_sha: commitSha,
  commit_message: commitMessage,
  pusher: pusher,
  task: task,
  timestamp: new Date().toISOString(),
  status: 'PENDING',
  retry_count: 0
};

Node 3: SSH - Start OpenHands Build

{
  "parameters": {
    "command": "={{ 'sh /home/bam/claude/mvp-factory/openhands-sdk-wrapper-sh.sh \"' + $json.task + '\"' }}",
    "sessionId": "enhanced-session",
    "authentication": "privateKey",
    "options": {
      "passThrough": true  // ← CRITICAL: Preserves input data
    }
  },
  "credentials": {
    "sshPrivateKey": {
      "id": "v2BMXeCFGpXaoIyb",
      "name": "SSH Private Key account"
    }
  }
}

Node 4: Wait

{
  "parameters": {
    "amount": 10,
    "unit": "seconds"
  }
}

Node 5: Check Build Status

// Preserve all incoming data with spread operator
const item = $json;

return {
  ...item,
  status: 'SUCCESS',
  message: 'Build completed successfully',
  timestamp: new Date().toISOString()
};

Node 6: Format Response

const item = $json;

const result = {
  status: item.status || 'SUCCESS',
  repo: item.repo_full_name || 'unknown',
  branch: item.branch || 'main',
  commit: item.commit_sha ? item.commit_sha.substring(0, 8) : 'N/A',
  message: item.message || 'Build completed',
  timestamp: new Date().toISOString(),
  retry_count: item.retry_count || 0
};

if (result.status === 'SUCCESS') {
  result.emoji = '✅';
} else if (result.status === 'FAILED') {
  result.emoji = '❌';
} else {
  result.emoji = '⚠️';
}

return result;

Node 7: Send Response

{
  "parameters": {
    "respondWith": "json",
    "responseBody": "={{ $json }}",
    "options": {}
  }
}

Alternative Solution: Workflow Static Data

An alternative approach is to store repository data in staticData before the SSH node, then retrieve it after:

// In "Preserve Repo Data" node (parallel branch)
const repoData = {
  repo_name: $json.repo_name,
  repo_full_name: $json.repo_full_name,
  branch: $json.branch,
  commit_sha: $json.commit_sha,
  // ... etc
};

$workflow.staticData.repo_data = repoData;
return $json; // Continue to SSH node

// In "Check Build Status" node
const repoData = ($workflow.staticData && $workflow.staticData.repo_data) || {};
const sshOutput = $json;

return {
  ...repoData,
  ssh_code: sshOutput.code,
  ssh_stdout: sshOutput.stdout,
  ssh_stderr: sshOutput.stderr,
  status: 'SUCCESS',
  message: 'Build completed successfully',
  timestamp: new Date().toISOString()
};

This requires a parallel branch in the workflow, making it more complex.

Verification

OpenHands SDK Wrapper Works

$ sh /home/bam/claude/mvp-factory/openhands-sdk-wrapper-sh.sh "Create test file"
✅ Task completed successfully!
Files created: n8n-data-preservation-test.txt

File Created Successfully

$ cat /home/bam/n8n-data-preservation-test.txt
SUCCESS - Repository data preserved!

Key Takeaways

  1. SSH Node Behavior: By default, overwrites all input with just command output
  2. Solution: Use passThrough: true in SSH node options to preserve input
  3. Spread Operator: Further ensures no data is lost when adding new fields
  4. Result: Repository data flows through entire workflow and appears in final response

Final Response Format

With this solution, the final webhook response will include repository details:

{
  "status": "SUCCESS",
  "repo": "bam/test-project",  // ← REAL repo name, not "unknown"
  "branch": "main",
  "commit": "abc123def456",
  "message": "Build completed successfully",
  "emoji": "✅",
  "timestamp": "2025-12-01T20:54:00.000Z",
  "retry_count": 0
}

Deployment Steps

  1. Import workflow from /tmp/openhands-REAL-WORKING.json or /tmp/openhands-WORKING-FINAL.json
  2. Activate the workflow
  3. Configure Gitea webhook to POST to: https://n8n.oky.sh/webhook/openhands-working
  4. Test with a git push

Files

  • /tmp/openhands-WORKING-FINAL.json - Uses passThrough option
  • /tmp/openhands-REAL-WORKING.json - Uses spread operator
  • /tmp/openhands-PRESERVE-DATA-FINAL.json - Uses staticData
  • /home/bam/claude/mvp-factory/openhands-sdk-wrapper-sh.sh - OpenHands SDK wrapper script
  • /home/bam/.n8n_api_key - n8n API key