Duplicate Code Opportunity
Summary
- Pattern:
streamFromContainer and tailFile in src/logs/log-streamer.ts both contain an identical 14-line block that: (1) attaches SIGINT/SIGTERM cleanup handlers, (2) runs a readline interface over a child process stdout, (3) silently swallows SIGTERM errors, and (4) removes the cleanup handlers in a finally block.
- Locations:
src/logs/log-streamer.ts:75–107 (streamFromContainer) and src/logs/log-streamer.ts:165–198 (tailFile)
- Impact: Signal-handler boilerplate is copy-pasted; a future bug (e.g. leaked handlers on re-throw) must be fixed in both places
Evidence
// streamFromContainer (lines ~78–107) — identical to tailFile (lines ~168–198)
const cleanup = () => { proc.kill('SIGTERM'); };
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
try {
if (proc.stdout) {
const rl = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
for await (const line of rl) {
processLine(line, formatter, parse, withPid);
}
}
await proc;
} catch (error) {
if (error instanceof Error && 'signal' in error && error.signal === 'SIGTERM') return;
throw error;
} finally {
process.off('SIGINT', cleanup);
process.off('SIGTERM', cleanup);
}
Suggested Refactoring
Extract into a private helper:
async function runWithSignalHandling(
proc: ExecaChildProcess,
formatter: LogFormatter,
parse: boolean,
withPid: boolean
): Promise<void> {
const cleanup = () => proc.kill('SIGTERM');
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
try {
if (proc.stdout) {
const rl = readline.createInterface({ input: proc.stdout, crlfDelay: Infinity });
for await (const line of rl) processLine(line, formatter, parse, withPid);
}
await proc;
} catch (error) {
if (error instanceof Error && 'signal' in error && error.signal === 'SIGTERM') return;
throw error;
} finally {
process.off('SIGINT', cleanup);
process.off('SIGTERM', cleanup);
}
}
Both streamFromContainer and tailFile then reduce to a single await runWithSignalHandling(proc, ...) call.
Affected Files
src/logs/log-streamer.ts — lines 54–107 (streamFromContainer) and 154–198 (tailFile)
Effort Estimate
Low
Detected by Duplicate Code Detector workflow. Run date: 2026-05-04
Generated by Duplicate Code Detector · ● 878.7K · ◷
Duplicate Code Opportunity
Summary
streamFromContainerandtailFileinsrc/logs/log-streamer.tsboth contain an identical 14-line block that: (1) attaches SIGINT/SIGTERM cleanup handlers, (2) runs a readline interface over a child process stdout, (3) silently swallows SIGTERM errors, and (4) removes the cleanup handlers in afinallyblock.src/logs/log-streamer.ts:75–107(streamFromContainer) andsrc/logs/log-streamer.ts:165–198(tailFile)Evidence
Suggested Refactoring
Extract into a private helper:
Both
streamFromContainerandtailFilethen reduce to a singleawait runWithSignalHandling(proc, ...)call.Affected Files
src/logs/log-streamer.ts— lines 54–107 (streamFromContainer) and 154–198 (tailFile)Effort Estimate
Low
Detected by Duplicate Code Detector workflow. Run date: 2026-05-04