Skip to content

Windows: Telegram voice-note STT + exec-approvals writer fail silently with EPERM on fsync/rename (no retry, no user feedback) #111476

Description

@ericfbrown1-boop

Environment

  • OS: Windows 11 Home 25H2 (Build 26200)
  • Node: v24.14.1
  • OpenClaw: 2026.5.2
  • State dir: C:\Users\<user>\.openclaw (default, inside Windows user profile)

Symptoms (real production case, 2026-07-19)

Two related failures observed within a 16-minute window on the same host, same directory tree:

A. Telegram voice-note fetch fails with EPERM: fsync

07:37:15  WARN  {module:telegram-auto-reply} media fetch failed
          { chatId: <...>, error: 'Error: EPERM: operation not permitted, fsync' }
          at bot-CW5ZEQ0V.js:2001 processInboundMessage

Downstream effect: the voice .ogg is not persisted, STT never runs, the user gets zero feedback — no error message, no retry, no incident record. In my case that was 3+ hours of unexplained silence.

B. exec-approvals.json atomic rewrite fails with EPERM: rename

07:21:54  ERROR [tools] exec failed: EPERM: operation not permitted, rename
          '.exec-approvals.<pid>.<uuid>.tmp' -> 'exec-approvals.json'

Downstream effect: the exec approvals state file isn't updated; tool approvals get out of sync.

Both errors were transient — subsequent operations succeeded — but neither retries.

Root cause

Well-documented Windows semantic: FlushFileBuffers (fsync) and MoveFileEx(REPLACE_EXISTING) return ERROR_ACCESS_DENIED (mapped by libuv to UV_EPERM) when any other process holds a handle on the target — even briefly. Windows file-system filter drivers (Search Indexer, AV mini-filters, cloud sync clients, backup helpers) routinely open handles on new files under the user profile for a few tens of milliseconds.

References:

Proposed fixes

1. Retry-with-backoff on transient fs errors inside .openclaw

Wrap fs.rename/fs.fsync/fs.renameSync/fs.close with an exponential backoff retry (25/50/100/200/400/800 ms, six attempts) on EPERM, EBUSY, EACCES. Only retry on Windows; other platforms don't hit this class. This is the graceful-fs pattern and is battle-tested by npm.

Two callsites are known to fail:

  • Telegram media fetch: bot-CW5ZEQ0V.js:~2001 (processInboundMessage)
  • exec-approvals writer (temp → rename pattern)

There are likely more (any atomic-replace of a file inside .openclaw).

2. Reply to sender on inbound processing failure

processInboundMessage currently swallows the error and logs media fetch failed. Nothing is sent back to Telegram. Suggested UX:

⚠️ I couldn't process your voice note (media fetch failed on my end). Please try sending it again.

Plus a structured entry in incidents.jsonl so the failure is visible to health monitors / RCA agents.

3. Refactor Telegram STT to stream in-memory

For voice notes destined for Whisper STT, there's no reason to persist the OGG to local disk at all. Fetch bytes into a Buffer, wrap as a Blob/Uploadable, and pass directly to openai.audio.transcriptions.create({ file: buffer }). This eliminates the failure mode entirely for STT — no disk, no fsync, no EPERM.

  • OpenAI SDK accepts Buffer / Uploadable: see OpenAI community thread
  • Telegram bot APIs support streaming from CDN without saving to disk

4. Allow relocating .openclaw off the user profile

For Windows, allow the state directory to be moved outside C:\Users\<user>\ (e.g., C:\ProgramData\openclaw\ or via OPENCLAW_HOME env var). This isolates OpenClaw's I/O from every user-profile scanner and dramatically reduces the transient-lock surface area.

Willing to submit a PR

Fixes 1 and 2 are small and localized. Happy to open a PR — please let me know if this project accepts external contributions and if there's a preferred pattern (graceful-fs dependency vs. inline retry helper).

Impact summary

  • User-visible: Voice-to-text silently broken on Windows hosts under any transient file lock. Users have no way to know it failed.
  • Reliability: exec-approvals state file can desync under the same class of race.
  • Diagnosability: Silent error-swallow makes this class of failure very hard to detect without external log grepping.

Related: workarounds users are applying today

While waiting for an upstream fix, I've locally:

  • Added FILE_ATTRIBUTE_NOT_CONTENT_INDEXED recursively on .openclaw (blocks Windows Search Indexer)
  • Installed Sysinternals procmon/handle for future locker attribution
  • Added a 15-min log watchdog for EPERM.*\.openclaw patterns
  • Added a 6-hour synthetic write+fsync health check into .openclaw\media\inbound

These are environmental band-aids. The real fix is application-layer retry + memory-only STT pipeline + non-silent failure UX.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦪 silver shellfishThin issue quality; more reproduction proof or environment detail is needed.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions