fix: Resolve spawn E2BIG by passing Hermes prompt via --query-file

Linux execve limits arguments to 128KB, causing Paperclip to fail with spawn E2BIG on large prompts. Adds runtime patch to write prompt to /tmp/hermes-prompt-*.txt and pass via Hermes native --query-file flag, while cleaning up file after execution and filtering oversized env vars.
This commit is contained in:
Jannik committed 2026-10-02 07:10:06 +02:00
1 parent f4d8b10dfb
commit ee101b4a73
3 files changed
+111 -7

No files matched your search

+8
View File
@@ -130,6 +130,14 @@ else
docker exec -u node "${PAPERCLIP_CONTAINER}" hermes config set model.provider auto 2>/dev/null || true
fi
# PaperClip Runtime-Patches anwenden (spawn E2BIG Fix für Hermes)
PATCH_SCRIPT="${SERVICES_DIR}/paperclip/patch_paperclip.js"
if [[ -f "${PATCH_SCRIPT}" ]]; then
log "Wende PaperClip-Patches im Container an (spawn E2BIG Fix)..."
docker cp "${PATCH_SCRIPT}" "${PAPERCLIP_CONTAINER}:/tmp/patch_paperclip.js"
docker exec -u root "${PAPERCLIP_CONTAINER}" node /tmp/patch_paperclip.js || true
fi
# Verifikation im Container
log "Verifiziere Hermes im PaperClip-Container:"
docker exec -u node "${PAPERCLIP_CONTAINER}" hermes --version 2>&1 || true
+94
View File
@@ -0,0 +1,94 @@
const fs = require('fs');
const path = require('path');
console.log('[Patch PaperClip] Starting runtime patches...');
// ---------------------------------------------------------------------------
// 1. Fix Hermes E2BIG: Write prompt to --query-file instead of passing in argv
// ---------------------------------------------------------------------------
const hermesExecutePath = '/app/packages/adapters/hermes/src/server/execute.ts';
if (fs.existsSync(hermesExecutePath)) {
let content = fs.readFileSync(hermesExecutePath, 'utf8');
// Replace "-q", prompt with --query-file
const oldArgs = 'const args: string[] = ["chat", "-q", prompt];';
const newArgs = `const promptFile = path.join("/tmp", \`hermes-prompt-\${ctx.runId || Date.now()}.txt\`);
await fs.writeFile(promptFile, prompt, "utf-8");
const args: string[] = ["chat", "--query-file", promptFile];`;
if (content.includes(oldArgs)) {
content = content.replace(oldArgs, newArgs);
// Add cleanup in finally block
const oldRun = ` const result = await runChildProcess(ctx.runId, hermesCmd, args, {
cwd,
env,
timeoutSec,
graceSec,
onLog: wrappedOnLog,
onSpawn: ctx.onSpawn,
});`;
const newRun = ` let result;
try {
result = await runChildProcess(ctx.runId, hermesCmd, args, {
cwd,
env,
timeoutSec,
graceSec,
onLog: wrappedOnLog,
onSpawn: ctx.onSpawn,
});
} finally {
await fs.unlink(promptFile).catch(() => {});
}`;
if (content.includes(oldRun)) {
content = content.replace(oldRun, newRun);
}
fs.writeFileSync(hermesExecutePath, content, 'utf8');
console.log('[Patch PaperClip] Successfully patched hermes execute.ts for --query-file (fixes spawn E2BIG)!');
} else {
console.log('[Patch PaperClip] hermes execute.ts already patched or target pattern not found.');
}
}
// ---------------------------------------------------------------------------
// 2. Fix server-utils E2BIG: filter monster environment variables (>32KB)
// ---------------------------------------------------------------------------
const serverUtilsPath = '/app/packages/adapter-utils/src/server-utils.ts';
if (fs.existsSync(serverUtilsPath)) {
let content = fs.readFileSync(serverUtilsPath, 'utf8');
const oldEnvLoop = ` for (const [key, value] of Object.entries(childEnv)) {
if (value === undefined) delete childEnv[key];
}`;
const newEnvLoop = ` for (const [key, value] of Object.entries(childEnv)) {
if (value === undefined || (typeof value === "string" && value.length > 32768)) delete childEnv[key];
}`;
if (content.includes(oldEnvLoop)) {
content = content.replace(oldEnvLoop, newEnvLoop);
fs.writeFileSync(serverUtilsPath, content, 'utf8');
console.log('[Patch PaperClip] Successfully patched server-utils.ts to guard against env E2BIG!');
} else {
console.log('[Patch PaperClip] server-utils.ts already patched or target pattern not found.');
}
}
// ---------------------------------------------------------------------------
// 3. Fix AI Connection endpoint for OpenRouter / custom base URLs
// ---------------------------------------------------------------------------
const aiConnPath = '/app/server/dist/routes/ai-connections.js';
if (fs.existsSync(aiConnPath)) {
let content = fs.readFileSync(aiConnPath, 'utf8');
const target = '"https://api.openai.com/v1/models"';
const replacement = 'process.env.OPENAI_BASE_URL ? process.env.OPENAI_BASE_URL + "/models" : "https://api.openai.com/v1/models"';
if (content.includes(target)) {
content = content.split(target).join(replacement);
fs.writeFileSync(aiConnPath, content, 'utf8');
console.log('[Patch PaperClip] Successfully patched ai-connections.js for OPENAI_BASE_URL!');
}
}
console.log('[Patch PaperClip] All patches completed.');
+9 -7
View File
@@ -26,14 +26,16 @@ notify() {
}
###############################################################################
# 1. LLM-Validierungs-Endpunkt patchen (OpenRouter/Mistral Workaround)
# Verhindert, dass das Onboarding-UI OPENAI_BASE_URL ignoriert.
# 1. PaperClip Patches anwenden (spawn E2BIG Fix + LLM Validierung)
###############################################################################
log "Patching LLM validation endpoint in Paperclip container..."
docker exec -u root "${CONTAINER}" sed -i \
's|"https://api.openai.com/v1/models"|process.env.OPENAI_BASE_URL ? process.env.OPENAI_BASE_URL + "/models" : "https://api.openai.com/v1/models"|g' \
/app/server/dist/routes/ai-connections.js || true
docker restart "${CONTAINER}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PATCH_SCRIPT="${SCRIPT_DIR}/patch_paperclip.js"
if [[ -f "${PATCH_SCRIPT}" ]]; then
log "Applying runtime patches to Paperclip container (fixes spawn E2BIG and AI validation)..."
docker cp "${PATCH_SCRIPT}" "${CONTAINER}:/tmp/patch_paperclip.js"
docker exec -u root "${CONTAINER}" node /tmp/patch_paperclip.js || warn "Patch script failed (non-fatal)"
docker restart "${CONTAINER}"
fi
###############################################################################
# 2. Auf PaperClip warten