Skip to content

readFileStringSafe does not catch EISDIR - TUI crashes on every launch with Unexpected server error (exit 133 / SIGTRAP) #32205

Description

@R1nlz3r

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 immediately

Also 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";      // ← caught

During instance bootstrap (Config.loadInstanceStateInstanceBootstrapInstanceStore.bootServer.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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions