fix: await onOutput chain in container runner to prevent duplicate delivery

Container runner was calling onOutput with fire-and-forget (void),
so work items were still in 'produced' status when the drain loop
checked after container exit. This caused the same message to be
delivered through both reviewer and owner channels.

Now chains onOutput calls and waits for completion before resolving,
matching the host-process runner's outputChain pattern.
This commit is contained in:
Eyejoker
2026-03-30 00:05:42 +09:00
parent 382c9f49d4
commit 60c3dd5fb9

View File

@@ -330,6 +330,11 @@ export async function runReviewerContainer(args: {
let lastOutput: AgentOutput | null = null;
let totalOutputSize = 0;
let parseBuffer = '';
// Chain onOutput calls so all async work (message delivery, DB writes)
// completes before the container exit handler runs. Without this,
// work items are still 'produced' when the drain loop checks, causing
// duplicate deliveries through different channels.
let outputChain = Promise.resolve();
// Streaming output: parse OUTPUT_START/END marker pairs
proc.stdout.on('data', (chunk: Buffer) => {
@@ -367,7 +372,9 @@ export async function runReviewerContainer(args: {
try {
const parsed = JSON.parse(json) as AgentOutput;
lastOutput = parsed;
void onOutput?.(parsed);
if (onOutput) {
outputChain = outputChain.then(() => onOutput(parsed));
}
} catch (err) {
logger.warn(
{ containerName, err },
@@ -421,7 +428,9 @@ export async function runReviewerContainer(args: {
);
if (lastOutput) {
resolve(lastOutput);
// Wait for all queued onOutput handlers to finish so work items
// are marked delivered before the caller proceeds to drain.
outputChain.then(() => resolve(lastOutput!));
return;
}