anomalyco / anomalyco/opencode
Desktop (Windows): EPERM reading .config/opencode/opencode.jsonc even though Node reads it fine; rename+recreate (identical bytes) fixes it
@Hona is already working on this.
Since Aug 7, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Problem
On Windows 11, the OpenCode Desktop app fails to start: the server returns HTTP 500 / "Unexpected server error", and the log shows EPERM while reading the global config file:
FileSystem.readFile(C:\Users\<user>\.config\opencode\opencode.jsonc)
-> EPERM: operation not permitted, open
This happens at startup during Config.getGlobal().
What I observed
- The file
opencode.jsoncexists and contains a valid{ "$schema": "https://opencode.ai/config.json" }. - Node reads it fine:
node -e "fs.readFileSync(process.env.USERPROFILE+'/.config/opencode/opencode.jsonc')"returns the content. - Permissions look fine; I already fixed broken ACLs on
.configand.local\share(takeown + icacls). Node, PowerShell and Explorer all read/write normally there. - Yet OpenCode's own read still returns EPERM. Node can read the same file, OpenCode cannot.
What I tried that fixed it
Renamed the file and let OpenCode recreate it:
Rename-Item "$env:USERPROFILE\.config\opencode\opencode.jsonc" opencode.jsonc.bak
After that OpenCode started fine and has been stable (survives restart and reboot). Notably:
- The recreated file is byte-for-byte identical to the old one (same SHA256), so it's not about content.
- Only the file's filesystem metadata changed. That means the old file's ACL/attributes were the problem, most likely because it was created while the container directory was still in a broken ACL state (the same state that earlier caused
EPERM: mkdir).
Suspected root cause
A Windows ACL edge case on that one config file, exposed by the way the bundled runtime opens files — not a bug in config parsing. Node opens with one access mask and succeeds; OpenCode's startup read is denied. I'm reporting it mainly so startup failures like this (and the opaque HTTP 500) are easier to diagnose.
Suggested improvement (optional)
packages/core/src/global.ts does await Promise.all([... fs.mkdir(...)]) for the config/data/state dirs with no error handling, so one EPERM aborts startup and shows only a bare HTTP 500. Wrapping each mkdir with a catch + a log line naming the directory (and the takeown/icacls hint) would make this class of failure self-explanatory.
Related: #36124 (embedded-runtime-on-Windows theme, though that one is a different root cause).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.