Repository navigation
Description
Description
readFileStringSafe does not catch EISDIR — TUI crashes on every launch with Unexpected server error (exit 133 / SIGTRAP)
Environment
| Field | Value |
|---|---|
| opencode version | 1.17.3, 1.17.4 (both confirmed) |
| Install method | Homebrew / npm global |
| OS | macOS 15.7.7 (24G720) — Intel x86_64 |
| Shell | fish |
| Terminal | Ghostty inside tmux |
Summary
The TUI fails immediately on every launch with:
Error: Unexpected server error. Check server logs for details.
at <anonymous> (/$bunfs/root/chunk-e6x2d5p2.js:8:7615)
at processTicksAndRejections (native:7:39)
The process exits with code 133 (SIGTRAP). The root cause is a one-line regression in packages/core/src/fs-util.ts: readFileStringSafe catches "NotFound" (ENOENT) but not "BadResource" (EISDIR). During instance bootstrap, opencode probes the config directory path itself as a legacy bare config file. That path is always a directory, so the read returns EISDIR, which goes uncaught, producing an unhandled promise rejection that Bun terminates with SIGTRAP.
Steps to reproduce
# Fresh install, no prior config
brew install opencode # or npm install -g opencode-ai
opencode
# → crashes immediatelyAlso reproducible by running opencode debug config:
Error: Unexpected error
BadResource: FileSystem.readFile (/Users/<you>/.config/opencode)
Expected behaviour
The TUI launches.
Actual behaviour
The process exits 133 / SIGTRAP on every launch. No amount of cleanup fixes it: the server recreates ~/.config/opencode/ as a directory at startup (via mkdir -p), so the failing probe always hits a directory and always throws.
Root cause
Regression introduced by commit 13ac849db ("refactor(config+core): drop ConfigPaths.readFile, add AppFileSystem.readFileStringSafe, flatten TuiConfig.loadState").
The old ConfigPaths.readFile caught all non-ENOENT errors and silently skipped them. The replacement, readFileStringSafe in packages/core/src/fs-util.ts, only catches "NotFound" (ENOENT):
// packages/core/src/fs-util.ts (current — broken)
const readFileStringSafe = Effect.fn("FileSystem.readFileStringSafe")(function* (path) {
return yield* fs
.readFileString(path)
.pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)))
})The platform error code mapping (same file) is:
case "EISDIR": return "BadResource"; // ← not caught
case "ENOENT": return "NotFound"; // ← caughtDuring instance bootstrap (Config.loadInstanceState → InstanceBootstrap → InstanceStore.boot → Server.listen), Config.get probes four paths for the global config. The fourth is the bare XDG path - $XDG_CONFIG_HOME/opencode (~/.config/opencode) — kept for backwards compatibility with the pre-directory config format. That path is a directory (the server creates it at startup for its own config files), so readFileString returns EISDIR → "BadResource" → not caught → unhandled rejection → Bun SIGTRAP.
Server log confirming this (visible with opencode serve --print-logs --log-level DEBUG):
message=loading path=/Users/<you>/.config/opencode/config.json ← ok (ENOENT, caught)
message=loading path=/Users/<you>/.config/opencode/opencode.json ← ok (ENOENT, caught)
message=loading path=/Users/<you>/.config/opencode/opencode.jsonc ← ok (ENOENT, caught)
message=loading path=/Users/<you>/.config/opencode ← EISDIR, NOT caught
message=failed error="PlatformError: BadResource: FileSystem.readFile (/Users/<you>/.config/opencode) (cause: Error: EISDIR: illegal operation on a directory, read)"
Full stack from the server log:
at Config.loadInstanceState (chunk-s77sw9gv.js:5:5063)
at Config.state
at Config.get
at InstanceBootstrap
at InstanceStore.boot
at InstanceStore.load
at Server.listen
Fix
One-line change in packages/core/src/fs-util.ts - add "BadResource" to the caught reasons:
const readFileStringSafe = Effect.fn("FileSystem.readFileStringSafe")(function* (path) {
return yield* fs
.readFileString(path)
- .pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)))
+ .pipe(
+ Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)),
+ Effect.catchReason("PlatformError", "BadResource", () => Effect.succeed(undefined)),
+ )
})"BadResource" covers both EISDIR and ENOTDIR, which is correct for a function named readFileStringSafe - if the path cannot be read as a file for any reason, returning undefined is the right behaviour.
Workaround (until fixed)
None that survive a restart. The server always recreates ~/.config/opencode/ as a directory at startup, so deleting the directory does not help.
Plugins
None
OpenCode version
1.17.4
Steps to reproduce
No response
Screenshot and/or share link
No response
Operating System
macOS 15.7.7 (24G720) - Intel x86_64
Terminal
Ghostty
Activity
mindofcharles commentedon Jul 3, 2026
macOS: 26.5.2 - ARM64
OpenCode: 1.17.13
macOS Terminal
The same issue exists.
github-actions commentedon Sep 2, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.