MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / readClientHello

Function readClientHello

src/mcp/daemon.ts:793–864  ·  view source on GitHub ↗

* Read the optional client-hello line a proxy sends after the daemon hello. * Always resolves (never rejects) — fail-safe by design, since every connection * funnels through here. Resolves with the peer pids when the first line is a * client-hello; otherwise resolves with null pids and unshifts t

(
  socket: net.Socket,
)

Source from the content-addressed store, hash-verified

791 * straddling a chunk boundary in the unshifted tail is never corrupted.
792 */
793function readClientHello(
794 socket: net.Socket,
795): Promise<{ pid: number | null; hostPid: number | null }> {
796 return new Promise((resolve) => {
797 let chunks: Buffer[] = [];
798 let total = 0;
799 let settled = false;
800 const finish = (
801 peers: { pid: number | null; hostPid: number | null },
802 putBack?: Buffer,
803 ) => {
804 if (settled) return;
805 settled = true;
806 // PAUSE before detaching: removing the last 'data' listener does NOT
807 // stop a flowing stream, so bytes arriving (or unshifted) in the gap
808 // between this handler and the session transport attaching were emitted
809 // to zero listeners and silently DISCARDED — and the listener swap left
810 // the socket's flow state wedged, never delivering to the new listener.
811 // A proxy whose client-hello arrived glued to the initialize hit this
812 // ~1-in-5 under load: the daemon answered nothing for the whole session
813 // (the #662 test flake, and real dead sessions behind it). Paused, the
814 // unshifted tail and any new bytes buffer; SocketTransport.start()
815 // resumes explicitly.
816 try { socket.pause(); } catch { /* stream already gone */ }
817 socket.removeListener('data', onData);
818 socket.removeListener('error', onEnd);
819 socket.removeListener('close', onEnd);
820 clearTimeout(timer);
821 if (process.env.CODEGRAPH_MCP_DEBUG) {
822 process.stderr.write(`[mcp-debug] clientHello finish pid=${String(peers.pid)} putBack=${putBack ? putBack.length : 0} flowing=${String(socket.readableFlowing)}\n`);
823 }
824 if (putBack && putBack.length > 0 && !socket.destroyed) {
825 try { socket.unshift(putBack); } catch { /* stream already gone */ }
826 }
827 resolve(peers);
828 };
829 const onData = (chunk: Buffer | string) => {
830 const buf = typeof chunk === 'string' ? Buffer.from(chunk, 'utf8') : chunk;
831 chunks.push(buf);
832 total += buf.length;
833 const all = chunks.length === 1 ? buf : Buffer.concat(chunks, total);
834 const nl = all.indexOf(0x0a); // '\n'
835 if (nl === -1) {
836 // No newline yet. If it's already too long to be a hello, it isn't one —
837 // hand the bytes back as data; otherwise keep accumulating.
838 if (total > MAX_HELLO_LINE_BYTES) finish({ pid: null, hostPid: null }, all);
839 else chunks = [all];
840 return;
841 }
842 const peers = parseClientHelloLine(all.subarray(0, nl).toString('utf8'));
843 if (peers) {
844 const tail = all.subarray(nl + 1);
845 finish(peers, tail.length > 0 ? tail : undefined);
846 } else {
847 // First line is not a client-hello (legacy/direct client) — hand the
848 // whole buffer back so the transport sees the message verbatim.
849 finish({ pid: null, hostPid: null }, all);
850 }

Callers 1

handleConnectionMethod · 0.85

Calls 2

finishFunction · 0.70
onMethod · 0.65

Tested by

no test coverage detected