Skip to content

tui: project configuration errors are not surfaced when opening a directory #55670

Open

Description

@martindisley

Summary

A missing file referenced by a project's agent configuration causes a server-side configuration error, but no error is shown in the TUI. The user instead appears to land in another location and cannot access the expected sessions.

Environment

  • OpenCode: 2.0.29, latest channel
  • OS: Darwin 27.0.0, arm64
  • Terminal: Ghostty per runtime diagnostics
  • Shell: zsh
  • Server: shared background service
  • Local plugins: clockwork_cal_refresh.ts, elm_costs.ts, word_count.ts
  • Not tested without plugins or in standalone mode

Reproduction

Observed with an existing project inside an iCloud-synced Obsidian directory:

  1. Project configuration contains an agent prompt reference:
    "prompt": "{file:../AI Orchestration/Agents/Work.md}"
  2. Move the referenced file without updating the configuration.
  3. Open OpenCode in the affected project.
  4. Observe that the expected sessions are inaccessible, without a visible configuration error.

These are inferred reproduction steps from the observed failure; a minimal isolated reproduction has not been tested.

Expected Behavior

Display an actionable configuration error identifying the configuration file and missing referenced file. Do not silently change location if project initialization fails.

Actual Behavior

No error was shown to the user. OpenCode appeared to open at "root" instead of the intended location; the exact fallback destination was not confirmed.

The server logged:

ConfigInvalidError
PlatformError: NotFound: FileSystem.readFile (<project>/AI Orchestration/Agents/Work.md)
ENOENT: no such file or directory
http.method=GET http.url=/api/fs/list http.status=500

Additional Context

  • The shared service remained healthy.
  • Existing project sessions were still retrievable through the API.
  • Opening a separate test directory worked.
  • After the user moved the referenced file, the affected project successfully resolved through /api/location. TUI recovery has not yet been confirmed.
  • The relationship between the configuration failure and apparent location fallback needs investigation.

Activity

  1. opencode-agent commented on Oct 15, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced this on 2.0.29.

    Steps (2.0.29):

    1. Create a project whose opencode.json has an agent with "prompt": "{file:../AI Orchestration/Agents/Work.md}", pointing at a file that doesn't exist.
    2. Run opencode in that project.

    Result: the TUI opens with the location shown as ~ instead of the project, and no configuration error is displayed. The same project with a valid reference opens normally. The recording and screenshots below show this.

    On the latest dev (4397ee4):

    • A missing referenced file no longer breaks the project's config. fix(core): substitute empty for missing config file references #55388, merged after 2.0.29, replaces it with an empty string and logs a warning. Your exact case should therefore behave better in the next release.
    • The TUI still falls back to ~ silently, without showing an error, when the project config fails for another reason. I tested a {file:} reference that points at a directory, and it still lands at ~ (last screenshot). So the main point of this issue, that config errors aren't shown and the location changes silently, still happens on dev.

    The script below reproduces both cases (REF=dir for the directory case).

    Related: #54786 and its open PR #54804, which cover broken config files being ignored without any message.

    recording.mp4

    Screenshot

    Screenshot

    Screenshot

    Reproduction script (drive.ts)
    // Repro for anomalyco/opencode#55670: a project config whose agent prompt references a
    // missing file makes the project's Location fail, and the TUI silently falls back to
    // another location instead of showing the configuration error.
    //
    // REF selects the reference: "missing" (default, the reported case) or "dir"
    // (a {file:} reference pointing at a directory, which still fails on latest dev), or "ok" (control).
    import { Effect } from "effect"
    import { OpenCodeDriver } from "opencode-drive"
    
    const ref =
      process.env.REF === "dir" ? "{file:./prompts}" : process.env.REF === "ok" ? "{file:./notes.md}" : "{file:../AI Orchestration/Agents/Work.md}"
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
        project: {
          git: true,
          files: {
            "opencode.json": JSON.stringify(
              { $schema: "https://opencode.ai/config.json", agent: { work: { prompt: ref } } },
              null,
              2,
            ),
            "prompts/README.md": "placeholder\n",
            "notes.md": "project file\n",
          },
        },
      },
      ({ ui, opencode }) =>
        Effect.gen(function* () {
          yield* Effect.sleep(4000)
          yield* ui.screenshot("tui-after-open")
          const frame = yield* ui.capture()
          yield* Effect.log("FRAME:\n" + JSON.stringify(frame).slice(0, 4000))
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. argszero commented on Oct 16, 2026

    @argszero
    Contributor

    I'd like to work on this.

    I traced the silent fallback to the TUI's startup location probe in packages/tui/src/app.tsx (~L218):

    const location = yield* Effect.tryPromise(() => api.file.list({ location: { directory: process.cwd() } })).pipe(
      Effect.map((response) => response.location),
      Effect.catch(() => Effect.tryPromise(() => api.location.get())),
    )

    The TUI resolves its starting location by listing files in process.cwd(). When that request fails for any reason — including the ConfigInvalidError that becomes the GET /api/fs/list 500 in the report above — the cause is discarded and the call silently falls back to api.location.get(), the server's default location. On a shared background service that default is the root, which is the "appeared to open at root" / location shown as ~ symptom, with no error shown.

    That also explains why #55388 (merged) improved the missing-file case without closing this issue: it removed one cause of the config failure, not the silent fallback. The directory-reference case reproduced above still fails and still lands at ~.

    Intended scope (small, and no behaviour change when the probe succeeds):

    • stop swallowing the cause in that probe, and surface an actionable error naming the configuration failure instead of quietly swapping the location.

    Related but distinct work already in flight: #54804 (broken config files ignored without a message) and #55940 (gating the recovery dialog in LocationProvider.sync on LocationNotFoundError). Neither covers this line — #55940 gates a different signal in the same file, so I will rebase on whichever lands first.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions