fix(infra): tolerate concurrent tmp dir repair

This commit is contained in:
Peter Steinberger
2026-04-30 15:27:41 +01:00
parent 11a56db5c1
commit 165d62b15f
3 changed files with 93 additions and 2 deletions

View File

@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
- Telegram: use durable message edits for streaming previews instead of native draft state, so generated replies no longer flicker through draft-to-message transitions that look like duplicates. (#75073) Thanks @obviyus.
- Telegram: echo preflighted DM voice-note transcripts back to the originating chat, including Telegram DM topic thread metadata, instead of only echoing later media-understanding transcripts. Fixes #75084. Thanks @M-Lietz.
- Web search: describe `web_search` as using the configured provider instead of hard-coding Brave when DuckDuckGo or another provider is active. Fixes #75088. Thanks @sun-rongyang.
- 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.
## 2026.4.29

View File

@@ -394,6 +394,82 @@ describe("resolvePreferredOpenClawTmpDir", () => {
expect(warn).toHaveBeenCalledWith(expect.stringContaining("tightened permissions on temp dir"));
});
it("uses /tmp/openclaw when another process tightened permissions before repair", () => {
const chmodSync = vi.fn();
const warn = vi.fn();
const tmpdir = vi.fn(() => "/var/fallback");
const states = [0o40777, 0o40700, 0o40700];
const lstatSync = vi.fn<NonNullable<TmpDirOptions["lstatSync"]>>((target: string) => {
if (target === POSIX_OPENCLAW_TMP_DIR) {
return makeDirStat({ mode: states.shift() ?? 0o40700 });
}
return secureDirStat();
});
const resolved = resolvePreferredOpenClawTmpDir({
accessSync: vi.fn(),
lstatSync,
chmodSync,
mkdirSync: vi.fn(),
getuid: vi.fn(() => 501),
tmpdir,
warn,
});
expect(resolved).toBe(POSIX_OPENCLAW_TMP_DIR);
expect(chmodSync).not.toHaveBeenCalled();
expect(warn).not.toHaveBeenCalled();
expect(tmpdir).not.toHaveBeenCalled();
});
it("uses fallback when another process tightened fallback permissions before repair", () => {
const fallbackPath = fallbackTmp();
const chmodSync = vi.fn();
const warn = vi.fn();
const states = [0o40777, 0o40700, 0o40700];
const resolved = resolveWithReadOnlyTmpFallback({
fallbackPath,
fallbackLstatSync: vi.fn(() => makeDirStat({ mode: states.shift() ?? 0o40700 })),
chmodSync,
warn,
});
expect(resolved).toBe(fallbackPath);
expect(chmodSync).not.toHaveBeenCalled();
expect(warn).not.toHaveBeenCalled();
});
it("uses /tmp/openclaw when chmod loses a concurrent repair race", () => {
const chmodSync = vi.fn((target: string, mode: number) => {
if (target === POSIX_OPENCLAW_TMP_DIR && mode === 0o700) {
throw nodeErrorWithCode("EPERM");
}
});
const warn = vi.fn();
const states = [0o40777, 0o40777, 0o40700];
const lstatSync = vi.fn<NonNullable<TmpDirOptions["lstatSync"]>>((target: string) => {
if (target === POSIX_OPENCLAW_TMP_DIR) {
return makeDirStat({ mode: states.shift() ?? 0o40700 });
}
return secureDirStat();
});
const resolved = resolvePreferredOpenClawTmpDir({
accessSync: vi.fn(),
lstatSync,
chmodSync,
mkdirSync: vi.fn(),
getuid: vi.fn(() => 501),
tmpdir: vi.fn(() => "/var/fallback"),
warn,
});
expect(resolved).toBe(POSIX_OPENCLAW_TMP_DIR);
expect(chmodSync).toHaveBeenCalledWith(POSIX_OPENCLAW_TMP_DIR, 0o700);
expect(warn).not.toHaveBeenCalled();
});
it("throws when the fallback directory cannot be created", () => {
expect(() =>
resolvePreferredOpenClawTmpDir({

View File

@@ -106,10 +106,24 @@ export function resolvePreferredOpenClawTmpDir(
if (uid !== undefined && typeof st.uid === "number" && st.uid !== uid) {
return false;
}
if (typeof st.mode !== "number" || (st.mode & 0o022) === 0) {
if (typeof st.mode !== "number") {
return false;
}
chmodSync(candidatePath, 0o700);
if ((st.mode & 0o022) === 0) {
return resolveDirState(candidatePath) === "available";
}
try {
chmodSync(candidatePath, 0o700);
} catch (chmodErr) {
if (
isNodeErrorWithCode(chmodErr, "EPERM") ||
isNodeErrorWithCode(chmodErr, "EACCES") ||
isNodeErrorWithCode(chmodErr, "ENOENT")
) {
return resolveDirState(candidatePath) === "available";
}
throw chmodErr;
}
warn(`[openclaw] tightened permissions on temp dir: ${candidatePath}`);
return resolveDirState(candidatePath) === "available";
} catch {