Skip to content

Commit 7fe4ba0

Browse files
Peetiegonzalezclaude
authored andcommitted
fix(gateway): add lightweight row path for sessions.list to reduce event-loop blocking
sessions.list calls buildGatewaySessionRow for every visible session, running transcript usage fallback, display model inference, cost/context recomputation, thinking level enumeration, agent runtime metadata, and plugin extension projection per row. On installs with 30-50+ sessions this blocks the event loop for 20-80+ seconds, starving Discord heartbeats and Control UI RPCs. Add skipTranscriptUsageFallback and lightweightListRow flags to buildGatewaySessionRow. In lightweight mode, skip transcript usage fallback, display model inference, cost/context recomputation, thinking level options, agent runtime metadata, and plugin extension projection. Use persisted entry fields directly for cost, tokens, and model identity. listSessionsFromStoreAsync now passes both flags for bulk list rows. Detail endpoints and single-row loads are unaffected. Observed improvement on a production install (33 sessions): sessions.list row construction dropped from ~82s to ~6s. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> (cherry picked from commit 803879e)
1 parent 4c9a557 commit 7fe4ba0

1 file changed

Lines changed: 48 additions & 39 deletions

File tree

src/gateway/session-utils.ts

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,8 +1430,12 @@ export function buildGatewaySessionRow(params: {
14301430
transcriptUsageMaxBytes?: number;
14311431
storeChildSessionsByKey?: Map<string, string[]>;
14321432
rowContext?: SessionListRowContext;
1433+
skipTranscriptUsageFallback?: boolean;
1434+
lightweightListRow?: boolean;
14331435
}): GatewaySessionRow {
14341436
const { cfg, storePath, store, key, entry } = params;
1437+
const lightweight = params.lightweightListRow === true;
1438+
const skipTranscriptUsage = params.skipTranscriptUsageFallback === true;
14351439
const now = params.now ?? Date.now();
14361440
const updatedAt = entry?.updatedAt ?? null;
14371441
const parsed = parseGroupKey(key);
@@ -1533,7 +1537,8 @@ export function buildGatewaySessionRow(params: {
15331537
entry,
15341538
}) === undefined;
15351539
const transcriptUsage =
1536-
needsTranscriptTotalTokens || needsTranscriptContextTokens || needsTranscriptEstimatedCostUsd
1540+
!skipTranscriptUsage &&
1541+
(needsTranscriptTotalTokens || needsTranscriptContextTokens || needsTranscriptEstimatedCostUsd)
15371542
? resolveTranscriptUsageFallback({
15381543
cfg,
15391544
key,
@@ -1577,36 +1582,39 @@ export function buildGatewaySessionRow(params: {
15771582
const latestCompactionCheckpoint = buildCompactionCheckpointPreview(
15781583
resolveLatestCompactionCheckpoint(entry),
15791584
);
1580-
const agentRuntime = resolveAgentRuntimeMetadata(cfg, sessionAgentId);
1585+
const agentRuntime = lightweight ? undefined : resolveAgentRuntimeMetadata(cfg, sessionAgentId);
15811586
const selectedOrRuntimeModelProvider = selectedModel?.provider ?? modelProvider;
15821587
const selectedOrRuntimeModel = selectedModel?.model ?? model;
1583-
const rowModelIdentity = resolveSessionDisplayModelIdentityRef({
1584-
cfg,
1585-
agentId: sessionAgentId,
1586-
provider: selectedOrRuntimeModelProvider,
1587-
model: selectedOrRuntimeModel,
1588-
});
1588+
const rowModelIdentity = lightweight
1589+
? { provider: selectedOrRuntimeModelProvider, model: selectedOrRuntimeModel }
1590+
: resolveSessionDisplayModelIdentityRef({
1591+
cfg,
1592+
agentId: sessionAgentId,
1593+
provider: selectedOrRuntimeModelProvider,
1594+
model: selectedOrRuntimeModel,
1595+
});
15891596
const rowModelProvider = rowModelIdentity.provider;
15901597
const rowModel = rowModelIdentity.model;
1591-
const estimatedCostUsd =
1592-
resolveEstimatedSessionCostUsd({
1593-
cfg,
1594-
provider: rowModelProvider,
1595-
model: rowModel,
1596-
entry,
1597-
}) ?? resolveNonNegativeNumber(transcriptUsage?.estimatedCostUsd);
1598-
const contextTokens =
1599-
resolvePositiveNumber(entry?.contextTokens) ??
1600-
resolvePositiveNumber(transcriptUsage?.contextTokens) ??
1601-
resolvePositiveNumber(
1602-
resolveContextTokensForModel({
1598+
const estimatedCostUsd = lightweight
1599+
? resolveNonNegativeNumber(entry?.estimatedCostUsd)
1600+
: resolveEstimatedSessionCostUsd({
16031601
cfg,
16041602
provider: rowModelProvider,
16051603
model: rowModel,
1606-
// Gateway/session listing is read-only; don't start async model discovery.
1607-
allowAsyncLoad: false,
1608-
}),
1609-
);
1604+
entry,
1605+
}) ?? resolveNonNegativeNumber(transcriptUsage?.estimatedCostUsd);
1606+
const contextTokens = lightweight
1607+
? resolvePositiveNumber(entry?.contextTokens)
1608+
: resolvePositiveNumber(entry?.contextTokens) ??
1609+
resolvePositiveNumber(transcriptUsage?.contextTokens) ??
1610+
resolvePositiveNumber(
1611+
resolveContextTokensForModel({
1612+
cfg,
1613+
provider: rowModelProvider,
1614+
model: rowModel,
1615+
allowAsyncLoad: false,
1616+
}),
1617+
);
16101618

16111619
let derivedTitle: string | undefined;
16121620
let lastMessagePreview: string | undefined;
@@ -1627,14 +1635,11 @@ export function buildGatewaySessionRow(params: {
16271635

16281636
const thinkingProvider = rowModelProvider ?? DEFAULT_PROVIDER;
16291637
const thinkingModel = rowModel ?? DEFAULT_MODEL;
1630-
const thinkingLevels = listThinkingLevelOptions(
1631-
thinkingProvider,
1632-
thinkingModel,
1633-
params.modelCatalog,
1634-
);
1635-
const pluginExtensions = entry
1636-
? projectPluginSessionExtensionsSync({ sessionKey: key, entry })
1637-
: [];
1638+
const thinkingLevels = lightweight
1639+
? []
1640+
: listThinkingLevelOptions(thinkingProvider, thinkingModel, params.modelCatalog);
1641+
const pluginExtensions =
1642+
!lightweight && entry ? projectPluginSessionExtensionsSync({ sessionKey: key, entry }) : [];
16381643

16391644
return {
16401645
key,
@@ -1662,13 +1667,15 @@ export function buildGatewaySessionRow(params: {
16621667
thinkingLevel: entry?.thinkingLevel,
16631668
thinkingLevels,
16641669
thinkingOptions: thinkingLevels.map((level) => level.label),
1665-
thinkingDefault: resolveGatewaySessionThinkingDefault({
1666-
cfg,
1667-
provider: thinkingProvider,
1668-
model: thinkingModel,
1669-
agentId: sessionAgentId,
1670-
modelCatalog: params.modelCatalog,
1671-
}),
1670+
thinkingDefault: lightweight
1671+
? entry?.thinkingLevel
1672+
: resolveGatewaySessionThinkingDefault({
1673+
cfg,
1674+
provider: thinkingProvider,
1675+
model: thinkingModel,
1676+
agentId: sessionAgentId,
1677+
modelCatalog: params.modelCatalog,
1678+
}),
16721679
fastMode: entry?.fastMode,
16731680
verboseLevel: entry?.verboseLevel,
16741681
traceLevel: entry?.traceLevel,
@@ -2016,6 +2023,8 @@ export async function listSessionsFromStoreAsync(params: {
20162023
transcriptUsageMaxBytes: sessionListTranscriptUsageMaxBytes,
20172024
storeChildSessionsByKey: getRowContext().storeChildSessionsByKey,
20182025
rowContext: getRowContext(),
2026+
skipTranscriptUsageFallback: true,
2027+
lightweightListRow: true,
20192028
});
20202029
if (
20212030
entry?.sessionId &&

0 commit comments

Comments
 (0)