# 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: ```json { "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: ```javascript // 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: ```json { "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: ```json { "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 ```json { "parameters": { "httpMethod": "POST", "path": "openhands-working", "options": {} } } ``` ### Node 2: Extract Repo Info ```javascript // 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 ```json { "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 ```json { "parameters": { "amount": 10, "unit": "seconds" } } ``` ### Node 5: Check Build Status ```javascript // 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 ```javascript 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 ```json { "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: ```javascript // 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** ```bash $ 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** ```bash $ 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: ```json { "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