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

推荐订阅源

雷峰网
雷峰网
爱范儿
爱范儿
宝玉的分享
宝玉的分享
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - Franky
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
人人都是产品经理
人人都是产品经理
阮一峰的网络日志
阮一峰的网络日志
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
博客园 - 聂微东
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
博客园 - 叶小钗
WordPress大学
WordPress大学
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
小众软件
小众软件
博客园 - 司徒正美
博客园 - 【当耐特】
IT之家
IT之家

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(auth): preserve non-signalable lock owners · openclaw...
steipete · 2026-05-14 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -0,0 +1,34 @@

1+

import { describe, expect, it } from "vitest";

2+

import { isPidDefinitelyDead, shouldRemoveDeadOwnerOrExpiredLock } from "./stale-lock-file.js";

3+
4+

describe("stale lock file ownership", () => {

5+

it("treats permission-denied process probes as not definitely dead", () => {

6+

expect(

7+

shouldRemoveDeadOwnerOrExpiredLock({

8+

payload: {

9+

pid: 123,

10+

createdAt: new Date(Date.now() - 60_000).toISOString(),

11+

},

12+

staleMs: 10,

13+

isPidDefinitelyDead: () => false,

14+

}),

15+

).toBe(false);

16+

});

17+
18+

it("only removes pid-owned locks when the owner is definitely dead", () => {

19+

expect(

20+

shouldRemoveDeadOwnerOrExpiredLock({

21+

payload: {

22+

pid: 123,

23+

createdAt: new Date(Date.now() - 60_000).toISOString(),

24+

},

25+

staleMs: 10,

26+

isPidDefinitelyDead: () => true,

27+

}),

28+

).toBe(true);

29+

});

30+
31+

it("treats invalid pids as definitely dead", () => {

32+

expect(isPidDefinitelyDead(-1)).toBe(true);

33+

});

34+

});

Original file line numberDiff line numberDiff line change

@@ -1,5 +1,4 @@

11

import fs from "node:fs/promises";

2-

import { isPidAlive as defaultIsPidAlive } from "../shared/pid-alive.js";

32
43

export type LockFileSnapshot = {

54

raw: string;

@@ -52,11 +51,11 @@ export function shouldRemoveDeadOwnerOrExpiredLock(params: {

5251

payload: Record<string, unknown> | null;

5352

staleMs: number;

5453

nowMs?: number;

55-

isPidAlive?: (pid: number) => boolean;

54+

isPidDefinitelyDead?: (pid: number) => boolean;

5655

}): boolean {

5756

const payload = readLockFileOwnerPayload(params.payload);

5857

if (payload?.pid) {

59-

return !(params.isPidAlive ?? defaultIsPidAlive)(payload.pid);

58+

return (params.isPidDefinitelyDead ?? isPidDefinitelyDead)(payload.pid);

6059

}

6160

if (payload?.createdAt) {

6261

const createdAt = Date.parse(payload.createdAt);

@@ -65,6 +64,18 @@ export function shouldRemoveDeadOwnerOrExpiredLock(params: {

6564

return false;

6665

}

6766
67+

export function isPidDefinitelyDead(pid: number): boolean {

68+

if (!Number.isInteger(pid) || pid <= 0) {

69+

return true;

70+

}

71+

try {

72+

process.kill(pid, 0);

73+

return false;

74+

} catch (err) {

75+

return (err as NodeJS.ErrnoException).code === "ESRCH";

76+

}

77+

}

78+
6879

export async function removeLockFileIfSnapshotMatches(params: {

6980

lockPath: string;

7081

snapshot: LockFileSnapshot;

Original file line numberDiff line numberDiff line change

@@ -69,7 +69,7 @@ describe("acquireFileLock", () => {

6969

stale: 10,

7070

} as const;

7171
72-

const deadPid = Number.MAX_SAFE_INTEGER;

72+

const deadPid = -1;

7373

await fs.writeFile(

7474

lockPath,

7575

JSON.stringify({ pid: deadPid, createdAt: new Date(Date.now() - 60_000).toISOString() }),

@@ -147,7 +147,7 @@ describe("acquireFileLock", () => {

147147

}

148148

})(),

149149

).rejects.toMatchObject({

150-

code: FILE_LOCK_STALE_ERROR_CODE,

150+

code: FILE_LOCK_TIMEOUT_ERROR_CODE,

151151

});

152152

await expect(fs.realpath(caught?.lockPath ?? "")).resolves.toBe(await fs.realpath(lockPath));

153153

await expect(fs.readFile(lockPath, "utf8")).resolves.toContain(`"pid":${process.pid}`);

Original file line numberDiff line numberDiff line change

@@ -5,11 +5,9 @@ import {

55

resetFileLockManagerForTest,

66

} from "@openclaw/fs-safe/file-lock";

77

import {

8-

readLockFileOwnerPayload,

98

removeReportedStaleLockIfStillStale,

109

shouldRemoveDeadOwnerOrExpiredLock,

1110

} from "../infra/stale-lock-file.js";

12-

import { isPidAlive } from "../shared/pid-alive.js";

1311
1412

export type FileLockOptions = {

1513

retries: {

@@ -48,15 +46,11 @@ async function shouldReclaimPluginLock(params: {

4846

staleMs: number;

4947

nowMs: number;

5048

}): Promise<boolean> {

51-

const payload = readLockFileOwnerPayload(params.payload);

52-

if (payload?.pid && !isPidAlive(payload.pid)) {

53-

return true;

54-

}

55-

if (payload?.createdAt) {

56-

const createdAt = Date.parse(payload.createdAt);

57-

return !Number.isFinite(createdAt) || params.nowMs - createdAt > params.staleMs;

58-

}

59-

return false;

49+

return shouldRemoveDeadOwnerOrExpiredLock({

50+

payload: params.payload,

51+

staleMs: params.staleMs,

52+

nowMs: params.nowMs,

53+

});

6054

}

6155
6256

function isFileLockError(error: unknown, code: string): boolean {