mvp-factory-openhands/N8N_DATA_PRESERVATION_SOLUT...

288 lines
7.5 KiB
Markdown

# 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