288 lines
7.5 KiB
Markdown
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
|
|
|