fix: quiet late ssh data frames after provider sessions

This commit is contained in:
Codex
2026-06-29 16:18:41 +00:00
parent f28e851f69
commit ef68531e17
2 changed files with 68 additions and 1 deletions
@@ -109,6 +109,15 @@ interface PendingSshDataFrame {
queuedAt: number;
}
type CompletedHostSshSessionReason = "exit" | "error" | "client-close";
interface CompletedHostSshSession {
dataChannelId: string;
closedAt: number;
reason: CompletedHostSshSessionReason;
exitCode: number | null;
}
interface SshDataChannel {
id: string;
socket: Socket;
@@ -154,6 +163,7 @@ interface DockerContainerInspectDetails {
}
const hostSshSessions = new Map<string, HostSshSession>();
const completedHostSshSessions = new Map<string, CompletedHostSshSession>();
const microserviceHttpCache = new Map<string, MicroserviceHttpCacheEntry>();
const microserviceHttpInFlight = new Map<string, Promise<JsonValue>>();
let providerEgressProxy: ProviderEgressProxyHandle | null = null;
@@ -163,6 +173,8 @@ let sshDataChannelSeq = 0;
let sshDataLastError: string | null = null;
const sshDataPendingSessionFrameMaxCount = 64;
const sshDataPendingSessionFrameMaxBytes = sshDataMaxPayloadBytes;
const completedHostSshSessionTtlMs = 60_000;
const completedHostSshSessionMaxCount = 512;
const microserviceForwardRequestHeaders = [
"accept",
"content-type",
@@ -696,6 +708,45 @@ function sendSshDataSessionFrame(sessionId: string, header: Record<string, unkno
return writeSshDataFrame(channel, { ...header, sessionId }, payload);
}
function pruneCompletedHostSshSessions(now = Date.now()): void {
for (const [sessionId, completed] of completedHostSshSessions) {
if (now - completed.closedAt > completedHostSshSessionTtlMs) completedHostSshSessions.delete(sessionId);
}
while (completedHostSshSessions.size > completedHostSshSessionMaxCount) {
const oldestSessionId = completedHostSshSessions.keys().next().value;
if (typeof oldestSessionId !== "string") break;
completedHostSshSessions.delete(oldestSessionId);
}
}
function rememberCompletedHostSshSession(
sessionId: string,
dataChannelId: string | undefined,
reason: CompletedHostSshSessionReason,
exitCode: number | null = null,
): void {
if (dataChannelId === undefined) return;
pruneCompletedHostSshSessions();
completedHostSshSessions.delete(sessionId);
completedHostSshSessions.set(sessionId, { dataChannelId, closedAt: Date.now(), reason, exitCode });
}
function getRecentCompletedHostSshSession(sessionId: string, dataChannelId: string): CompletedHostSshSession | null {
const completed = completedHostSshSessions.get(sessionId);
if (completed === undefined) return null;
const ageMs = Date.now() - completed.closedAt;
if (ageMs > completedHostSshSessionTtlMs) {
completedHostSshSessions.delete(sessionId);
return null;
}
if (completed.dataChannelId !== dataChannelId) return null;
return completed;
}
function isSshDataFrameIgnorableAfterSessionClosed(type: string): boolean {
return type === "input" || type === "eof" || type === "resize" || type === "close";
}
function pendingSshDataFrameBytes(channel: SshDataChannel): number {
return channel.pendingFrames.reduce((total, frame) => total + frame.payload.length, 0);
}
@@ -748,6 +799,19 @@ function handleSshDataFrame(channel: SshDataChannel, header: Record<string, unkn
bufferSshDataFrameBeforeSessionReady(channel, sessionId, type, header, payload);
return;
}
const completed = getRecentCompletedHostSshSession(sessionId, channel.id);
if (completed !== null && isSshDataFrameIgnorableAfterSessionClosed(type)) {
logger("debug", "ssh_data_frame_ignored_after_session_closed", {
channelId: channel.id,
sessionId,
type,
reason: completed.reason,
exitCode: completed.exitCode,
ageMs: Date.now() - completed.closedAt,
payloadBytes: payload.length,
});
return;
}
logger("warn", "ssh_data_session_missing", { channelId: channel.id, sessionId, type });
return;
}
@@ -1935,11 +1999,13 @@ function startHostSshSession(message: CoreHostSshOpenMessage): void {
signal: null,
at: new Date().toISOString(),
});
rememberCompletedHostSshSession(message.sessionId, message.dataChannelId, "exit", typeof exitCode === "number" ? exitCode : null);
hostSshSessions.delete(message.sessionId);
releaseSshDataChannel(message.dataChannelId, message.sessionId);
})
.catch((error) => {
sendHostSshError(message.sessionId, error);
rememberCompletedHostSshSession(message.sessionId, message.dataChannelId, "error");
hostSshSessions.delete(message.sessionId);
releaseSshDataChannel(message.dataChannelId, message.sessionId);
});
@@ -1953,6 +2019,7 @@ function startHostSshSession(message: CoreHostSshOpenMessage): void {
function closeHostSshSession(sessionId: string): void {
const session = hostSshSessions.get(sessionId);
if (session === undefined) return;
rememberCompletedHostSshSession(sessionId, session.dataChannelId, "client-close");
hostSshSessions.delete(sessionId);
if (session.dataChannelId !== undefined) releaseSshDataChannel(session.dataChannelId, sessionId);
session.proc.kill("SIGTERM");