fix: use temp file for AI check prompt to avoid E2BIG spawn error

The AI idle checker was passing the prompt (including up to 16KB of terminal
buffer) as a command-line argument to bash -c, which exceeded the Linux
argument size limit causing "spawn E2BIG" errors.

Now writes the prompt to a temp file and pipes it to claude via stdin,
avoiding the argument size limit entirely. Temp file is cleaned up both
in the normal case (by the shell command) and on cancellation/timeout.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-25 06:54:56 +01:00
co-authored by Claude Opus 4.5
parent 706392d264
commit 0aca55b099
3 changed files with 25 additions and 9 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
Claudeman is a Claude Code session manager with a web interface and autonomous Ralph Loop. It spawns Claude CLI processes via PTY, streams output in real-time via SSE, and supports scheduled/timed runs.
**Version**: 0.1341
**Version**: 0.1342
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claudeman",
"version": "0.1341",
"version": "0.1342",
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
"type": "module",
"main": "dist/index.js",
+23 -7
View File
@@ -143,6 +143,7 @@ export class AiIdleChecker extends EventEmitter {
// Active check state
private checkScreenName: string | null = null;
private checkTempFile: string | null = null;
private checkPromptFile: string | null = null;
private checkPollTimer: NodeJS.Timeout | null = null;
private checkTimeoutTimer: NodeJS.Timeout | null = null;
private checkStartTime: number = 0;
@@ -312,21 +313,25 @@ export class AiIdleChecker extends EventEmitter {
// Build the prompt
const prompt = AI_CHECK_PROMPT.replace('{TERMINAL_BUFFER}', trimmed);
// Generate temp file and screen name
// Generate temp files and screen name
const shortId = this.sessionId.slice(0, 8);
const timestamp = Date.now();
this.checkTempFile = join(tmpdir(), `claudeman-aicheck-${shortId}-${timestamp}.txt`);
this.checkPromptFile = join(tmpdir(), `claudeman-aicheck-prompt-${shortId}-${timestamp}.txt`);
this.checkScreenName = `claudeman-aicheck-${shortId}`;
// Ensure temp file exists (empty) so we can poll it
// Ensure output temp file exists (empty) so we can poll it
writeFileSync(this.checkTempFile, '');
// Build the command - escape the prompt for shell
const escapedPrompt = prompt.replace(/'/g, "'\\''");
// Write prompt to file to avoid E2BIG error (argument list too long)
// The prompt can be 16KB+ which exceeds shell argument limits
writeFileSync(this.checkPromptFile, prompt);
// Build the command - read prompt from file via stdin to avoid argument size limits
const modelArg = `--model ${this.config.model}`;
const augmentedPath = getAugmentedPath();
const claudeCmd = `claude -p ${modelArg} --output-format text '${escapedPrompt}'`;
const fullCmd = `export PATH="${augmentedPath}"; ${claudeCmd} > "${this.checkTempFile}" 2>&1; echo "${DONE_MARKER}" >> "${this.checkTempFile}"`;
const claudeCmd = `cat "${this.checkPromptFile}" | claude -p ${modelArg} --output-format text`;
const fullCmd = `export PATH="${augmentedPath}"; ${claudeCmd} > "${this.checkTempFile}" 2>&1; echo "${DONE_MARKER}" >> "${this.checkTempFile}"; rm -f "${this.checkPromptFile}"`;
// Spawn screen
try {
@@ -430,7 +435,7 @@ export class AiIdleChecker extends EventEmitter {
this.checkScreenName = null;
}
// Delete temp file
// Delete temp files
if (this.checkTempFile) {
try {
if (existsSync(this.checkTempFile)) {
@@ -441,6 +446,17 @@ export class AiIdleChecker extends EventEmitter {
}
this.checkTempFile = null;
}
if (this.checkPromptFile) {
try {
if (existsSync(this.checkPromptFile)) {
unlinkSync(this.checkPromptFile);
}
} catch {
// Best effort cleanup
}
this.checkPromptFile = null;
}
}
private handleError(errorMsg: string): void {