chore: sync core lib and CLAUDE.md from agent-core#34
Conversation
There was a problem hiding this comment.
Code Review
This pull request unifies repository intelligence by folding repo-map into lib/repo-intel and introducing an opt-in embedding pipeline with binary resolution, orchestration, and post-init enrichment helpers. The review feedback highlights several robustness improvements to prevent process crashes and hangs, including handling missing redirect locations, catching unhandled EPIPE stream errors on child process stdin, implementing a fail-fast mechanism in the dual-process orchestrator pipe, and adding null guards to prevent TypeError crashes.
| var loc = res.headers.location; | ||
| if (loc && !loc.startsWith('https://')) { | ||
| reject(new Error('Refusing non-HTTPS redirect to ' + loc)); | ||
| return; | ||
| } | ||
| request(loc, redirectCount + 1); | ||
| return; |
There was a problem hiding this comment.
If the Location header is missing or undefined (e.g., due to a malformed redirect response), calling request(loc, ...) will pass undefined as the URL. This causes https.get(undefined, ...) to throw a synchronous TypeError inside the asynchronous response callback, resulting in an unhandled exception that crashes the Node.js process. Adding a check to ensure loc is present before proceeding prevents this crash.
| var loc = res.headers.location; | |
| if (loc && !loc.startsWith('https://')) { | |
| reject(new Error('Refusing non-HTTPS redirect to ' + loc)); | |
| return; | |
| } | |
| request(loc, redirectCount + 1); | |
| return; | |
| var loc = res.headers.location; | |
| if (!loc) { | |
| reject(new Error('Redirect status ' + sc + ' received but no Location header found')); | |
| return; | |
| } | |
| if (!loc.startsWith('https://')) { | |
| reject(new Error('Refusing non-HTTPS redirect to ' + loc)); | |
| return; | |
| } | |
| request(loc, redirectCount + 1); | |
| return; |
| const tar = cp.spawn('tar', ['xz', '-C', tarDest], { | ||
| stdio: ['pipe', 'pipe', 'pipe'] | ||
| }); | ||
| let stderr = ''; | ||
| tar.stderr.on('data', function (d) { stderr += d; }); | ||
| tar.stdin.write(buf); | ||
| tar.stdin.end(); |
There was a problem hiding this comment.
If the tar process fails to spawn or exits immediately, writing to tar.stdin can emit an unhandled EPIPE or broken pipe error on the stream. In Node.js, unhandled stream errors crash the process. Registering a dummy error listener on tar.stdin prevents these crashes, as any actual process failures are already handled by the close or error events on the child process itself.
const tar = cp.spawn('tar', ['xz', '-C', tarDest], {
stdio: ['pipe', 'pipe', 'pipe']
});
tar.stdin.on('error', function () {}); // Prevent unhandled EPIPE crashes
let stderr = '';
tar.stderr.on('data', function (d) { stderr += d; });
tar.stdin.write(buf);
tar.stdin.end();| const proc = cp.spawn(binPath, args, { | ||
| stdio: ['pipe', 'pipe', 'pipe'], | ||
| windowsHide: true | ||
| }); | ||
| let stdout = ''; |
There was a problem hiding this comment.
Similar to the tar extraction helper, if the agent-analyzer process exits immediately with an error, writing to proc.stdin can emit an unhandled EPIPE error on the stream and crash the Node.js process. Registering a dummy error listener on proc.stdin prevents this crash.
| const proc = cp.spawn(binPath, args, { | |
| stdio: ['pipe', 'pipe', 'pipe'], | |
| windowsHide: true | |
| }); | |
| let stdout = ''; | |
| const proc = cp.spawn(binPath, args, { | |
| stdio: ['pipe', 'pipe', 'pipe'], | |
| windowsHide: true | |
| }); | |
| proc.stdin.on('error', () => {}); // Prevent unhandled EPIPE crashes | |
| let stdout = ''; |
| function maybeFinish() { | ||
| if (settled || embedExit === null || setExit === null) return; | ||
| if (embedExit !== 0) { | ||
| return done(new Error( | ||
| embedBinary.EMBED_BINARY_NAME + ' exited ' + embedExit + | ||
| (embedStderr.trim() ? ': ' + embedStderr.trim().slice(0, 500) : '') | ||
| )); | ||
| } | ||
| if (setExit !== 0) { | ||
| return done(new Error( | ||
| 'agent-analyzer set-embeddings exited ' + setExit + | ||
| (setStderr.trim() ? ': ' + setStderr.trim().slice(0, 500) : '') | ||
| )); | ||
| } | ||
| const m = setStdout.match(/(\d+)\s+files?/); | ||
| done(null, { files: m ? parseInt(m[1], 10) : undefined }); | ||
| } |
There was a problem hiding this comment.
The current maybeFinish implementation requires both processes to exit (embedExit !== null && setExit !== null) before checking for failures. If one process fails early (e.g., set-embeddings exits with an error), the other process might keep running or hang waiting for input/output, causing the promise to hang indefinitely. Implementing a fail-fast approach allows the orchestrator to immediately reject the promise and terminate the surviving sibling process upon any failure.
| function maybeFinish() { | |
| if (settled || embedExit === null || setExit === null) return; | |
| if (embedExit !== 0) { | |
| return done(new Error( | |
| embedBinary.EMBED_BINARY_NAME + ' exited ' + embedExit + | |
| (embedStderr.trim() ? ': ' + embedStderr.trim().slice(0, 500) : '') | |
| )); | |
| } | |
| if (setExit !== 0) { | |
| return done(new Error( | |
| 'agent-analyzer set-embeddings exited ' + setExit + | |
| (setStderr.trim() ? ': ' + setStderr.trim().slice(0, 500) : '') | |
| )); | |
| } | |
| const m = setStdout.match(/(\d+)\s+files?/); | |
| done(null, { files: m ? parseInt(m[1], 10) : undefined }); | |
| } | |
| function maybeFinish() { | |
| if (settled) return; | |
| if (embedExit !== null && embedExit !== 0) { | |
| return done(new Error( | |
| embedBinary.EMBED_BINARY_NAME + ' exited ' + embedExit + | |
| (embedStderr.trim() ? ': ' + embedStderr.trim().slice(0, 500) : '') | |
| )); | |
| } | |
| if (setExit !== null && setExit !== 0) { | |
| return done(new Error( | |
| 'agent-analyzer set-embeddings exited ' + setExit + | |
| (setStderr.trim() ? ': ' + setStderr.trim().slice(0, 500) : '') | |
| )); | |
| } | |
| if (embedExit === 0 && setExit === 0) { | |
| const m = setStdout.match(/(\d+)\s+files?/); | |
| done(null, { files: m ? parseInt(m[1], 10) : undefined }); | |
| } | |
| } |
| function topPaths(repoIntelData, n) { | ||
| const fa = repoIntelData.fileActivity || {}; |
There was a problem hiding this comment.
If repoIntelData is null or undefined (which can happen if loadRaw returns null when the map file is missing or unreadable), accessing repoIntelData.fileActivity will throw a TypeError and crash the execution. Adding a guard ensures the function handles missing data gracefully.
| function topPaths(repoIntelData, n) { | |
| const fa = repoIntelData.fileActivity || {}; | |
| function topPaths(repoIntelData, n) { | |
| const fa = (repoIntelData && repoIntelData.fileActivity) || {}; |
Automated sync of lib/ and CLAUDE.md from agent-core.