mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:20:44 +00:00
fix(media): ignore EPERM during best-effort fsync
This commit is contained in:
@@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Control UI: render Dream Diary prose through the sanitized markdown pipeline, so diary bold/italic/header markdown no longer appears as literal source text. Fixes #62413.
|
||||
- Control UI: render tool results whose output arrives as text-block arrays and give expanded tool output a scrollable block, so read/exec output remains visible in WebChat. Fixes #77054.
|
||||
- MCP: include serialized conversation/message payloads in the primary text content for `conversations_list` and `messages_read`, while preserving `structuredContent` for capable clients. Fixes #77024.
|
||||
- Media: treat `EPERM` from the post-write media fsync step as best-effort, allowing WebChat and channel uploads to finish on Windows filesystems that reject `fsync` after a successful write. Fixes #76844.
|
||||
- Diagnostics: keep webhook/message OTEL attributes and Prometheus delivery labels low-cardinality and omit raw chat/message IDs from spans, so progress-draft and message-tool modes do not leak high-cardinality messaging identifiers.
|
||||
- Google Meet: stop advertising legacy `mode: "realtime"` to agents and config UIs, while keeping it as a hidden compatibility alias for `mode: "agent"`, so new joins use the STT -> OpenClaw agent -> TTS path instead of selecting the direct realtime voice fallback.
|
||||
- Google Meet: add `chrome.audioBufferBytes` for generated command-pair SoX audio commands and lower the default buffer from SoX's 8192 bytes to 4096 bytes to reduce Chrome talk-back latency.
|
||||
|
||||
@@ -341,6 +341,35 @@ describe("media store", () => {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "saves buffers when the best-effort fsync step reports EPERM",
|
||||
run: async () => {
|
||||
await withTempStore(async (store) => {
|
||||
const originalOpen = fs.open.bind(fs);
|
||||
vi.spyOn(fs, "open").mockImplementation(async (...args) => {
|
||||
const handle = await originalOpen(...args);
|
||||
const filePath = args[0];
|
||||
if (
|
||||
typeof filePath === "string" &&
|
||||
filePath.includes(`${path.sep}fsync-eperm${path.sep}`)
|
||||
) {
|
||||
vi.spyOn(handle, "sync").mockRejectedValueOnce(
|
||||
Object.assign(new Error("operation not permitted"), { code: "EPERM" }),
|
||||
);
|
||||
}
|
||||
return handle;
|
||||
});
|
||||
|
||||
const saved = await store.saveMediaBuffer(
|
||||
Buffer.from("docx"),
|
||||
"application/zip",
|
||||
"fsync-eperm",
|
||||
);
|
||||
|
||||
await expect(fs.readFile(saved.path, "utf8")).resolves.toBe("docx");
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rejects traversal media subdirs before saving buffers",
|
||||
run: async () => {
|
||||
|
||||
@@ -351,7 +351,7 @@ async function writeSavedMediaBuffer(params: {
|
||||
await fs.writeFile(tempDest, params.buffer, { mode: MEDIA_FILE_MODE });
|
||||
const handle = await fs.open(tempDest, "r");
|
||||
try {
|
||||
await handle.sync();
|
||||
await syncSavedMediaHandle(handle);
|
||||
} finally {
|
||||
await handle.close();
|
||||
}
|
||||
@@ -364,6 +364,17 @@ async function writeSavedMediaBuffer(params: {
|
||||
return dest;
|
||||
}
|
||||
|
||||
async function syncSavedMediaHandle(handle: fs.FileHandle): Promise<void> {
|
||||
try {
|
||||
await handle.sync();
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException | undefined)?.code === "EPERM") {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export type SaveMediaSourceErrorCode =
|
||||
| "invalid-path"
|
||||
| "not-found"
|
||||
|
||||
Reference in New Issue
Block a user