惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
D
Docker
腾讯CDC
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
The Cloudflare Blog
Martin Fowler
Martin Fowler
MongoDB | Blog
MongoDB | Blog
博客园 - Franky
博客园 - 三生石上(FineUI控件)
Recent Announcements
Recent Announcements
F
Fortinet All Blogs
IT之家
IT之家
WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
爱范儿
爱范儿
Microsoft Azure Blog
Microsoft Azure Blog
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
小众软件
小众软件
N
Netflix TechBlog - Medium
T
Tailwind CSS Blog
Engineering at Meta
Engineering at Meta
博客园 - 【当耐特】

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(gateway): show config recovery validation details (#7...
amknight · 2026-05-01 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -39,6 +39,7 @@ Docs: https://docs.openclaw.ai

3939

- Infra/tmp: tolerate concurrent temp-dir permission repairs by rechecking directories that another process already tightened, so parallel ACP subprocess startup no longer throws `Unsafe fallback OpenClaw temp dir`. Fixes #66867. Thanks @Kane808-AI and @jarvisz8.

4040

- Agents/compaction: add an opt-in `agents.defaults.compaction.midTurnPrecheck` mid-turn precheck that detects tool-loop context pressure and triggers compaction before the next tool call instead of waiting for end-of-turn. (#73499) Thanks @marchpure and @haoxingjun.

4141

- Gateway/approvals: let loopback token/password-backed native approval clients resolve exec approvals without attaching stale paired Gateway identities, while remote and unauthenticated approval clients keep normal device identity behavior. (#74472)

42+

- Gateway/config: include rejected validation paths in foreground and service last-known-good recovery logs plus main-agent notices, so unsupported direct edits explain which key caused restore instead of looking like silent reversion. Fixes #75060. Thanks @amknight.

4243
4344

## 2026.4.29

4445
Original file line numberDiff line numberDiff line change

@@ -341,6 +341,7 @@ Look for:

341341

- `.clobbered.*` exists → an external direct edit or startup read was restored.

342342

- `.rejected.*` exists → an OpenClaw-owned config write failed schema or clobber checks before commit.

343343

- `Config write rejected:` → the write tried to drop required shape, shrink the file sharply, or persist invalid config.

344+

- `Rejected validation details:` → the recovery log or main-agent notice includes the schema path that caused the restore, such as `agents.defaults.execution` or `gateway.auth.password.source`.

344345

- `missing-meta-vs-last-good`, `gateway-mode-missing-vs-last-good`, or `size-drop-vs-last-good:*` → startup treated the current file as clobbered because it lost fields or size compared with the last-known-good backup.

345346

- `Config last-known-good promotion skipped` → the candidate contained redacted secret placeholders such as `***`.

346347
Original file line numberDiff line numberDiff line change

@@ -408,7 +408,7 @@ describe("gateway run option collisions", () => {

408408

},

409409

});

410410

expect(gatewayLogMessages).toContain(

411-

"gateway: restored invalid effective config from last-known-good backup: /tmp/openclaw-test-missing-config.json",

411+

"gateway: restored invalid effective config from last-known-good backup: /tmp/openclaw-test-missing-config.json; Rejected validation details: <root>: JSON5 parse failed.",

412412

);

413413

expect(startGatewayServer).toHaveBeenCalledWith(

414414

19170,

Original file line numberDiff line numberDiff line change

@@ -8,6 +8,7 @@ import type {

88

GatewayBindMode,

99

GatewayTailscaleMode,

1010

} from "../../config/config.js";

11+

import { formatConfigIssueSummary } from "../../config/issue-format.js";

1112

import { CONFIG_PATH, resolveGatewayPort, resolveStateDir } from "../../config/paths.js";

1213

import type { OpenClawConfig } from "../../config/types.openclaw.js";

1314

import { hasConfiguredSecretInput } from "../../config/types.secrets.js";

@@ -290,8 +291,12 @@ async function readGatewayStartupConfig(params: {

290291

}),

291292

);

292293

if (recovered) {

294+

const issueSummary = formatConfigIssueSummary([

295+

...invalidSnapshot.issues,

296+

...invalidSnapshot.legacyIssues,

297+

]);

293298

gatewayLog.warn(

294-

`gateway: restored invalid effective config from last-known-good backup: ${invalidSnapshot.path}`,

299+

`gateway: restored invalid effective config from last-known-good backup: ${invalidSnapshot.path}${issueSummary ? `; Rejected validation details: ${issueSummary}.` : ""}`,

295300

);

296301

try {

297302

const { writeRestartSentinel } = await import("../../infra/restart-sentinel.js");

Original file line numberDiff line numberDiff line change

@@ -177,7 +177,7 @@ vi.mock("./progress.js", () => ({

177177

}));

178178
179179

vi.mock("../config/io.js", () => ({

180-

getRuntimeConfig: loadConfigMock,

180+

readBestEffortConfig: loadConfigMock,

181181

}));

182182
183183

vi.mock("../infra/net/proxy/proxy-lifecycle.js", () => ({

Original file line numberDiff line numberDiff line change

@@ -342,11 +342,11 @@ export async function runCli(argv: string[] = process.argv) {

342342

handle?.kill("SIGTERM");

343343

};

344344

if (shouldStartProxyForCli(normalizedArgv)) {

345-

const [{ getRuntimeConfig }, { startProxy }] = await Promise.all([

345+

const [{ readBestEffortConfig }, { startProxy }] = await Promise.all([

346346

import("../config/io.js"),

347347

import("../infra/net/proxy/proxy-lifecycle.js"),

348348

]);

349-

const config = getRuntimeConfig();

349+

const config = await readBestEffortConfig();

350350

proxyHandle = await startProxy(config?.proxy ?? undefined);

351351

}

352352
Original file line numberDiff line numberDiff line change

@@ -341,6 +341,9 @@ describe("config observe recovery", () => {

341341

expect(warn).toHaveBeenCalledWith(

342342

expect.stringContaining("Config auto-restored from last-known-good:"),

343343

);

344+

expect(warn).toHaveBeenCalledWith(

345+

expect.stringContaining("Rejected validation details: gateway.mode: Expected string."),

346+

);

344347

const observe = await readLastObserveEvent(auditPath);

345348

expect(observe?.restoredFromBackup).toBe(true);

346349

expect(observe?.restoredBackupPath).toBe(resolveLastKnownGoodConfigPath(configPath));

Original file line numberDiff line numberDiff line change

@@ -6,6 +6,7 @@ import {

66

appendConfigAuditRecordSync,

77

type ConfigObserveAuditRecord,

88

} from "./io.audit.js";

9+

import { formatConfigIssueSummary } from "./issue-format.js";

910

import { resolveStateDir } from "./paths.js";

1011

import {

1112

isPluginLocalInvalidConfigSnapshot,

@@ -1068,8 +1069,9 @@ export async function recoverConfigFromLastKnownGood(params: {

10681069

});

10691070

await deps.fs.promises.copyFile(lastGoodPath, snapshot.path);

10701071

await deps.fs.promises.chmod?.(snapshot.path, 0o600).catch(() => {});

1072+

const issueSummary = formatConfigIssueSummary([...snapshot.issues, ...snapshot.legacyIssues]);

10711073

deps.logger.warn(

1072-

`Config auto-restored from last-known-good: ${snapshot.path} (${params.reason})`,

1074+

`Config auto-restored from last-known-good: ${snapshot.path} (${params.reason})${issueSummary ? `; Rejected validation details: ${issueSummary}.` : ""}`,

10731075

);

10741076

await appendConfigAuditRecord(

10751077

createConfigObserveAuditAppendParams(deps, {

Original file line numberDiff line numberDiff line change

@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";

22

import {

33

formatConfigIssueLine,

44

formatConfigIssueLines,

5+

formatConfigIssueSummary,

56

normalizeConfigIssue,

67

normalizeConfigIssuePath,

78

normalizeConfigIssues,

@@ -47,6 +48,20 @@ describe("config issue format", () => {

4748

).toBe("- gateway.\\nbind: bad\\r\\n\\tvalue");

4849

});

4950
51+

it("formats concise issue summaries", () => {

52+

expect(formatConfigIssueSummary([])).toBeNull();

53+

expect(

54+

formatConfigIssueSummary(

55+

[

56+

{ path: "", message: "root broken" },

57+

{ path: "gateway.auth.password.source", message: "Required" },

58+

{ path: "agents.defaults.execution", message: "Unrecognized key" },

59+

],

60+

{ maxIssues: 2 },

61+

),

62+

).toBe("<root>: root broken; gateway.auth.password.source: Required; and 1 more");

63+

});

64+
5065

it("normalizes issue metadata for machine output", () => {

5166

expect(

5267

normalizeConfigIssue({

Original file line numberDiff line numberDiff line change

@@ -1,7 +1,7 @@

11

import { sanitizeTerminalText } from "../terminal/safe-text.js";

22

import type { ConfigValidationIssue } from "./types.js";

33
4-

type ConfigIssueLineInput = {

4+

export type ConfigIssueLineInput = {

55

path?: string | null;

66

message: string;

77

};

@@ -10,6 +10,10 @@ type ConfigIssueFormatOptions = {

1010

normalizeRoot?: boolean;

1111

};

1212
13+

type ConfigIssueSummaryOptions = ConfigIssueFormatOptions & {

14+

maxIssues?: number;

15+

};

16+
1317

export function normalizeConfigIssuePath(path: string | null | undefined): string {

1418

if (typeof path !== "string") {

1519

return "<root>";

@@ -66,3 +70,23 @@ export function formatConfigIssueLines(

6670

): string[] {

6771

return issues.map((issue) => formatConfigIssueLine(issue, marker, opts));

6872

}

73+
74+

export function formatConfigIssueSummary(

75+

issues: ReadonlyArray<ConfigIssueLineInput>,

76+

opts: ConfigIssueSummaryOptions = {},

77+

): string | null {

78+

if (issues.length === 0) {

79+

return null;

80+

}

81+

const maxIssueCandidate = Math.floor(opts.maxIssues ?? 5);

82+

const maxIssues = Number.isFinite(maxIssueCandidate) ? Math.max(1, maxIssueCandidate) : 5;

83+

const visibleIssues = issues.slice(0, maxIssues);

84+

const lines = formatConfigIssueLines(visibleIssues, "", {

85+

normalizeRoot: opts.normalizeRoot ?? true,

86+

});

87+

const hiddenIssueCount = issues.length - visibleIssues.length;

88+

if (hiddenIssueCount <= 0) {

89+

return lines.join("; ");

90+

}

91+

return `${lines.join("; ")}; and ${hiddenIssueCount} more`;

92+

}