Skip to content

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

Closed as not planned

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.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

  1. mindofcharles commented on Jul 3, 2026

    @mindofcharles

    macOS: 26.5.2 - ARM64
    OpenCode: 1.17.13
    macOS Terminal

    The same issue exists.

  2. github-actions commented on Sep 2, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions