Skip to content

Commit 1497425

Browse files
committed
fix(gateway): trim startup config imports
1 parent acbf57b commit 1497425

31 files changed

Lines changed: 111 additions & 67 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ Docs: https://docs.openclaw.ai
1111
- Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C `stream_messages` streaming with a `StreamingController` lifecycle manager, unified `sendMedia` with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via `createEngineAdapters()`. (#70624) Thanks @cxyhhhhh.
1212
- Gateway/runtime: reuse the current plugin metadata snapshot for provider discovery so repeated model-provider discovery avoids rebuilding plugin manifest metadata. Thanks @shakkernerd.
1313
- Gateway/startup: pass the plugin metadata snapshot from config validation into plugin bootstrap so startup reuses one manifest product instead of rebuilding plugin metadata. Thanks @shakkernerd.
14+
- ACP/runtime: add an opt-in bundled Coven backend extension that routes ACP coding sessions through a local Coven daemon when `acp.backend="coven"`, while preserving the existing ACPX backend as the default fallback path. Thanks @BunsDev.
1415

1516
### Fixes
1617

18+
- Gateway/startup: keep hot Gateway boot paths on leaf config imports and add max-RSS reporting to the gateway startup bench so low-memory startup regressions are visible before release. Thanks @vincentkoc.
19+
- ACP/runtime: harden the opt-in Coven backend with workspace-confined launch paths, home-expanded Coven socket config, bounded socket responses, sanitized daemon output, and controlled polling failure handling. Thanks @BunsDev.
1720
- Agents/LSP: terminate bundled stdio LSP process trees during runtime disposal and Gateway shutdown, so nested children such as `tsserver` do not survive stop or restart. Fixes #72357. Thanks @ai-hpc and @bittoby.
1821

1922
## 2026.4.26

scripts/bench-gateway-startup.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
1+
import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
22
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
33
import { request } from "node:http";
44
import { createServer } from "node:net";
@@ -23,6 +23,7 @@ type GatewaySample = {
2323
exitCode: number | null;
2424
firstOutputMs: number | null;
2525
healthz: ProbeResult;
26+
maxRssMb: number | null;
2627
outputTail: string;
2728
readyLogMs: number | null;
2829
readyz: ProbeResult;
@@ -45,6 +46,7 @@ type CaseResult = {
4546
summary: {
4647
firstOutputMs: SummaryStats | null;
4748
healthzMs: SummaryStats | null;
49+
maxRssMb: SummaryStats | null;
4850
readyLogMs: SummaryStats | null;
4951
readyzMs: SummaryStats | null;
5052
startupTrace: Record<string, SummaryStats>;
@@ -253,6 +255,11 @@ function summarizeCase(benchCase: GatewayBenchCase, samples: GatewaySample[]): C
253255
.map((sample) => sample.healthz.ms)
254256
.filter((value): value is number => typeof value === "number"),
255257
),
258+
maxRssMb: summarizeNumbers(
259+
samples
260+
.map((sample) => sample.maxRssMb)
261+
.filter((value): value is number => typeof value === "number"),
262+
),
256263
readyLogMs: summarizeNumbers(
257264
samples
258265
.map((sample) => sample.readyLogMs)
@@ -275,13 +282,27 @@ function formatMs(value: number | null): string {
275282
return `${value.toFixed(1)}ms`;
276283
}
277284

285+
function formatMb(value: number | null): string {
286+
if (value == null) {
287+
return "n/a";
288+
}
289+
return `${value.toFixed(1)}MB`;
290+
}
291+
278292
function formatStats(stats: SummaryStats | null): string {
279293
if (!stats) {
280294
return "n/a";
281295
}
282296
return `p50=${formatMs(stats.p50)} avg=${formatMs(stats.avg)} min=${formatMs(stats.min)} max=${formatMs(stats.max)}`;
283297
}
284298

299+
function formatMemoryStats(stats: SummaryStats | null): string {
300+
if (!stats) {
301+
return "n/a";
302+
}
303+
return `p50=${formatMb(stats.p50)} avg=${formatMb(stats.avg)} min=${formatMb(stats.min)} max=${formatMb(stats.max)}`;
304+
}
305+
285306
async function getFreePort(): Promise<number> {
286307
return new Promise((resolve, reject) => {
287308
const server = createServer();
@@ -400,6 +421,7 @@ function sanitizedEnv(
400421
OPENCLAW_GATEWAY_STARTUP_TRACE: "1",
401422
OPENCLAW_HOME: root,
402423
OPENCLAW_LOCAL_CHECK: "0",
424+
OPENCLAW_NO_RESPAWN: "1",
403425
OPENCLAW_STATE_DIR: path.join(root, "state"),
404426
OPENCLAW_TEST_DISABLE_UPDATE_CHECK: "1",
405427
...benchCase.env,
@@ -475,6 +497,21 @@ function parseStartupTraceMetrics(raw: string): Array<{ key: string; value: numb
475497
return metrics;
476498
}
477499

500+
function readProcessRssMb(pid: number | undefined): number | null {
501+
if (!pid || process.platform === "win32") {
502+
return null;
503+
}
504+
const result = spawnSync("ps", ["-o", "rss=", "-p", String(pid)], {
505+
encoding: "utf8",
506+
stdio: ["ignore", "pipe", "ignore"],
507+
});
508+
if (result.status !== 0) {
509+
return null;
510+
}
511+
const rssKb = Number.parseInt(result.stdout.trim(), 10);
512+
return Number.isFinite(rssKb) && rssKb > 0 ? rssKb / 1024 : null;
513+
}
514+
478515
async function runGatewaySample(options: {
479516
benchCase: GatewayBenchCase;
480517
entry: string;
@@ -489,6 +526,7 @@ async function runGatewaySample(options: {
489526
const startupTrace: Record<string, number> = {};
490527
const output: string[] = [];
491528
let firstOutputMs: number | null = null;
529+
let maxRssMb: number | null = null;
492530
let readyLogMs: number | null = null;
493531
let childExited = false;
494532

@@ -510,6 +548,15 @@ async function runGatewaySample(options: {
510548
],
511549
{ cwd: process.cwd(), detached: process.platform !== "win32", env },
512550
);
551+
const sampleRss = () => {
552+
const rssMb = readProcessRssMb(child.pid);
553+
if (rssMb != null) {
554+
maxRssMb = maxRssMb == null ? rssMb : Math.max(maxRssMb, rssMb);
555+
}
556+
};
557+
sampleRss();
558+
const rssTimer = setInterval(sampleRss, 100);
559+
rssTimer.unref?.();
513560
const childExitPromise = new Promise<{ exitCode: number | null; signal: string | null }>(
514561
(resolve) => {
515562
child.once("exit", (exitCode, signal) => {
@@ -555,13 +602,16 @@ async function runGatewaySample(options: {
555602
}),
556603
]);
557604
const exit = await stopChild(child);
605+
clearInterval(rssTimer);
606+
sampleRss();
558607
await childExitPromise.catch(() => null);
559608
rmSync(root, { force: true, maxRetries: 3, recursive: true, retryDelay: 100 });
560609

561610
return {
562611
exitCode: exit.exitCode,
563612
firstOutputMs,
564613
healthz,
614+
maxRssMb,
565615
outputTail: output.join("").split(/\r?\n/u).slice(-20).join("\n"),
566616
readyLogMs,
567617
readyz,
@@ -588,11 +638,11 @@ async function runCase(options: {
588638
if (index >= options.warmup) {
589639
samples.push(sample);
590640
console.log(
591-
`[gateway-startup-bench] ${options.benchCase.id} run ${samples.length}/${options.runs}: healthz=${formatMs(sample.healthz.ms)} readyz=${formatMs(sample.readyz.ms)} readyLog=${formatMs(sample.readyLogMs)}`,
641+
`[gateway-startup-bench] ${options.benchCase.id} run ${samples.length}/${options.runs}: healthz=${formatMs(sample.healthz.ms)} readyz=${formatMs(sample.readyz.ms)} readyLog=${formatMs(sample.readyLogMs)} rss=${formatMb(sample.maxRssMb)}`,
592642
);
593643
} else {
594644
console.log(
595-
`[gateway-startup-bench] ${options.benchCase.id} warmup ${index + 1}/${options.warmup}: healthz=${formatMs(sample.healthz.ms)} readyz=${formatMs(sample.readyz.ms)}`,
645+
`[gateway-startup-bench] ${options.benchCase.id} warmup ${index + 1}/${options.warmup}: healthz=${formatMs(sample.healthz.ms)} readyz=${formatMs(sample.readyz.ms)} rss=${formatMb(sample.maxRssMb)}`,
596646
);
597647
}
598648
}
@@ -605,6 +655,7 @@ function printResult(result: CaseResult): void {
605655
console.log(` /healthz: ${formatStats(result.summary.healthzMs)}`);
606656
console.log(` ready log: ${formatStats(result.summary.readyLogMs)}`);
607657
console.log(` /readyz: ${formatStats(result.summary.readyzMs)}`);
658+
console.log(` max RSS: ${formatMemoryStats(result.summary.maxRssMb)}`);
608659
const trace = Object.entries(result.summary.startupTrace)
609660
.filter(([name]) => !name.endsWith(".total"))
610661
.toSorted((a, b) => (b[1].avg ?? 0) - (a[1].avg ?? 0))

src/gateway/auth-resolve.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type {
22
GatewayAuthConfig,
33
GatewayTailscaleMode,
44
GatewayTrustedProxyConfig,
5-
} from "../config/config.js";
5+
} from "../config/types.gateway.js";
66
import { resolveSecretInputRef } from "../config/types.secrets.js";
77
import { resolveGatewayCredentialsFromValues } from "./credentials.js";
88

src/gateway/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { IncomingMessage } from "node:http";
2-
import type { GatewayAuthConfig, GatewayTrustedProxyConfig } from "../config/config.js";
2+
import type { GatewayAuthConfig, GatewayTrustedProxyConfig } from "../config/types.gateway.js";
33
import { readTailscaleWhoisIdentity, type TailscaleWhoisIdentity } from "../infra/tailscale.js";
44
import { safeEqualSecret } from "../security/secret-equal.js";
55
import {

src/gateway/config-reload.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import { isDeepStrictEqual } from "node:util";
22
import chokidar from "chokidar";
33
import { bumpSkillsSnapshotVersion } from "../agents/skills/refresh-state.js";
4-
import type {
5-
OpenClawConfig,
6-
ConfigFileSnapshot,
7-
ConfigWriteNotification,
8-
GatewayReloadMode,
9-
} from "../config/config.js";
10-
import {
11-
resolveConfigWriteFollowUp,
12-
shouldAttemptLastKnownGoodRecovery,
13-
} from "../config/config.js";
4+
import type { ConfigWriteNotification } from "../config/io.js";
145
import { formatConfigIssueLines } from "../config/issue-format.js";
6+
import { shouldAttemptLastKnownGoodRecovery } from "../config/recovery-policy.js";
7+
import { resolveConfigWriteFollowUp } from "../config/runtime-snapshot.js";
8+
import type { GatewayReloadMode } from "../config/types.gateway.js";
9+
import type { ConfigFileSnapshot, OpenClawConfig } from "../config/types.openclaw.js";
1510
import { isPlainObject } from "../utils.js";
1611
import {
1712
buildGatewayReloadPlan,

src/gateway/embeddings-http.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Buffer } from "node:buffer";
22
import type { IncomingMessage, ServerResponse } from "node:http";
33
import { resolveAgentDir } from "../agents/agent-scope.js";
44
import { resolveMemorySearchConfig } from "../agents/memory-search.js";
5-
import { getRuntimeConfig } from "../config/config.js";
5+
import { getRuntimeConfig } from "../config/io.js";
66
import type { OpenClawConfig } from "../config/types.openclaw.js";
77
import { formatErrorMessage } from "../infra/errors.js";
88
import { logWarn } from "../logger.js";

src/gateway/exec-approval-ios-push.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getRuntimeConfig } from "../config/config.js";
1+
import { getRuntimeConfig } from "../config/io.js";
22
import {
33
hasEffectivePairedDeviceRole,
44
listDevicePairing,

src/gateway/http-auth-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { IncomingMessage, ServerResponse } from "node:http";
2-
import { getRuntimeConfig } from "../config/config.js";
2+
import { getRuntimeConfig } from "../config/io.js";
33
import type { OpenClawConfig } from "../config/types.openclaw.js";
44
import {
55
normalizeLowercaseStringOrEmpty,

src/gateway/http-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
parseModelRef,
88
resolveDefaultModelForAgent,
99
} from "../agents/model-selection.js";
10-
import { getRuntimeConfig } from "../config/config.js";
10+
import { getRuntimeConfig } from "../config/io.js";
1111
import { buildAgentMainSessionKey, normalizeAgentId } from "../routing/session-key.js";
1212
import {
1313
normalizeLowercaseStringOrEmpty,

src/gateway/mcp-http.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
type IncomingMessage,
55
type ServerResponse,
66
} from "node:http";
7-
import { getRuntimeConfig } from "../config/config.js";
7+
import { getRuntimeConfig } from "../config/io.js";
88
import { isTruthyEnvValue } from "../infra/env.js";
99
import { formatErrorMessage } from "../infra/errors.js";
1010
import { logDebug, logWarn } from "../logger.js";

0 commit comments

Comments
 (0)