fix: treat signal-killed agents as success when output was delivered
Two bugs fixed: 1. agent-runner: process close handler only checked `code !== 0`, but signal kills (SIGTERM/SIGKILL from post-close cleanup) set code=null which was misclassified as error even after successful output delivery. Now checks `code === null && signal` and resolves as success when hadStreamingOutput is true. 2. message-agent-executor: wrappedOnOutput persisted newSessionId before checking for poisoned session, allowing a stale session to be re-saved after clearSession. Reordered to check poison first and guard persist with !resetSessionRequested. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -269,7 +269,7 @@ export async function runAgentProcess(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
proc.on('close', (code) => {
|
proc.on('close', (code, signal) => {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
const duration = Date.now() - startTime;
|
const duration = Date.now() - startTime;
|
||||||
|
|
||||||
@@ -286,6 +286,7 @@ export async function runAgentProcess(
|
|||||||
`Process: ${processName}`,
|
`Process: ${processName}`,
|
||||||
`Duration: ${duration}ms`,
|
`Duration: ${duration}ms`,
|
||||||
`Exit Code: ${code}`,
|
`Exit Code: ${code}`,
|
||||||
|
`Signal: ${signal}`,
|
||||||
`Had Streaming Output: ${hadStreamingOutput}`,
|
`Had Streaming Output: ${hadStreamingOutput}`,
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
);
|
);
|
||||||
@@ -299,6 +300,7 @@ export async function runAgentProcess(
|
|||||||
processName,
|
processName,
|
||||||
duration,
|
duration,
|
||||||
code,
|
code,
|
||||||
|
signal,
|
||||||
},
|
},
|
||||||
'Agent timed out after output (idle cleanup)',
|
'Agent timed out after output (idle cleanup)',
|
||||||
);
|
);
|
||||||
@@ -316,6 +318,49 @@ export async function runAgentProcess(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Signal kill (SIGTERM/SIGKILL) from post-close cleanup or service
|
||||||
|
// restart. When the agent already delivered streaming output this is
|
||||||
|
// normal lifecycle — not an error.
|
||||||
|
if (code === null && signal) {
|
||||||
|
if (hadStreamingOutput) {
|
||||||
|
logger.info(
|
||||||
|
{
|
||||||
|
group: group.name,
|
||||||
|
chatJid: input.chatJid,
|
||||||
|
runId: input.runId,
|
||||||
|
processName,
|
||||||
|
duration,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
'Agent terminated by signal after output delivery (normal cleanup)',
|
||||||
|
);
|
||||||
|
outputChain.then(() => {
|
||||||
|
resolve({ status: 'success', result: null, newSessionId });
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// No output delivered before signal kill — genuine error
|
||||||
|
logger.error(
|
||||||
|
{
|
||||||
|
group: group.name,
|
||||||
|
chatJid: input.chatJid,
|
||||||
|
runId: input.runId,
|
||||||
|
processName,
|
||||||
|
duration,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
'Agent killed by signal before producing output',
|
||||||
|
);
|
||||||
|
outputChain.then(() => {
|
||||||
|
resolve({
|
||||||
|
status: 'error',
|
||||||
|
result: null,
|
||||||
|
error: `Agent killed by ${signal} before producing output`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
const logFile = path.join(
|
const logFile = path.join(
|
||||||
logsDir,
|
logsDir,
|
||||||
@@ -335,6 +380,7 @@ export async function runAgentProcess(
|
|||||||
`AgentType: ${group.agentType || 'claude-code'}`,
|
`AgentType: ${group.agentType || 'claude-code'}`,
|
||||||
`Duration: ${duration}ms`,
|
`Duration: ${duration}ms`,
|
||||||
`Exit Code: ${code}`,
|
`Exit Code: ${code}`,
|
||||||
|
`Signal: ${signal}`,
|
||||||
``,
|
``,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -140,9 +140,6 @@ export async function runAgentForGroup(
|
|||||||
|
|
||||||
const wrappedOnOutput = onOutput
|
const wrappedOnOutput = onOutput
|
||||||
? async (output: AgentOutput) => {
|
? async (output: AgentOutput) => {
|
||||||
if (persistSessionIds && output.newSessionId) {
|
|
||||||
deps.persistSession(group.folder, output.newSessionId);
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
persistSessionIds &&
|
persistSessionIds &&
|
||||||
isClaudeCodeAgent &&
|
isClaudeCodeAgent &&
|
||||||
@@ -150,6 +147,13 @@ export async function runAgentForGroup(
|
|||||||
) {
|
) {
|
||||||
resetSessionRequested = true;
|
resetSessionRequested = true;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
persistSessionIds &&
|
||||||
|
output.newSessionId &&
|
||||||
|
!resetSessionRequested
|
||||||
|
) {
|
||||||
|
deps.persistSession(group.folder, output.newSessionId);
|
||||||
|
}
|
||||||
const evaluation = evaluateStreamedOutput(output, streamedState, {
|
const evaluation = evaluateStreamedOutput(output, streamedState, {
|
||||||
agentType: isClaudeCodeAgent ? 'claude-code' : 'codex',
|
agentType: isClaudeCodeAgent ? 'claude-code' : 'codex',
|
||||||
provider,
|
provider,
|
||||||
|
|||||||
Reference in New Issue
Block a user