Skip to content

500 error on POST /session/{id}/command when session.directory points to a moved/deleted project #35427

Closed

Description

@enihcam

Bug

POST /session/{id}/command returns HTTP 500 when the session's stored directory in the database points to a path that no longer exists (e.g. the project was moved or deleted after the session was created).

Steps to Reproduce

  1. Open a project at /path/to/old-location in opencode (TUI or web UI)
  2. Send a command — works fine
  3. Move or delete the project directory: mv /path/to/old-location /path/to/new-location
  4. Restart opencode (opencode web), open the web UI
  5. The old session appears in the session list
  6. Type a message and send → HTTP 500 returned
  7. noReply: true messages (e.g. /compact) succeed because they bypass the code path that validates the directory

Root Cause

The WorkspaceRoutingMiddleware (workspace-routing.ts:182) extracts session?.directory from the session's database row and sets it as the routing directory:

RequestPlan.Local({ directory: session?.directory || defaultDirectory(request, url) })

This directory is then used by InstanceContextMiddleware (instance-context.ts:29) to load the instance context via store.load({ directory }), which initializes a FileSystem service rooted at that path. When the directory no longer exists, FileSystem.realPath() fails with ENOENT.

Because SystemPrompt.environment() (system.ts:58-94) has signature Effect<string[]> (no error type), the Fail from FileSystem.realPath is converted to a Die defect. This Die bypasses the handler's Effect.mapError(() => new HttpApiError.BadRequest({})) (session.ts:338) — which only catches Fail causes — and hits the default 500 error handler.

Error chain:

SessionHttpApi.command
  → SessionPrompt.command → prompt → loop → ensureRunning → runLoop
  → SystemPrompt.environment()
  → ctx.directory = InstanceState.context.directory (from stale session row)
  → LocationServiceMap.get(ref) → FileSystem.realPath(stale directory) → ENOENT
  → Die (not Fail) → bypasses Effect.mapError(→BadRequest) → 500

Location in code

  • packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts:182 — session?.directory is the source of the stale path
  • packages/opencode/src/server/routes/instance/httpapi/middleware/instance-context.ts:29 — store.load({ directory }) fails when directory doesnt exist
  • packages/opencode/src/session/system.ts:58-94 — SystemPrompt.environment() converts the error to a Die
  • packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:338 — Effect.mapError(→BadRequest) insufficient for Die

Suggested Fix

Either:

A) Gracefully handle missing directory in SystemPrompt.environment()

Wrap the FileSystem.realPath call inside LocationServiceMap.get(ref) with Effect.catchAll so a missing reference directory is silently skipped instead of crashing the entire request. This is the more robust fix as it also handles other scenarios where a reference path becomes unreachable.

B) Allow the session directory to be updated

Add an API or CLI command to change a session's directory field in the database when the project moves. This would also help users who reorganize their filesystem.

C) Validate session directory at session list time

When listing sessions, detect if the stored directory is unreachable and surface a warning so the user knows which sessions are orphaned.

Workaround

Update the session's directory column in the SQLite database directly:

UPDATE session SET directory = '/path/to/new-location' WHERE id = '<session-id>';

The database is located at ~/.local/share/opencode/opencode.db.

Activity

  1. github-actions commented on Jul 5, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If this is the same underlying problem, consider adding your findings there (especially the SystemPrompt.environment() → Die defect chain you identified) rather than tracking it separately.

  2. robertn702 commented on Jul 19, 2026

    @robertn702

    Confirming this on the desktop app (macOS, OpenCode Desktop 1.18.3, ai.opencode.desktop) — with an extra wrinkle: in the desktop app the stale path lives in two places, and the second one poisons every launch.

    1. session.directory (same as this issue) — sessions bound to a deleted Superset worktree subfolder (/Users/robert/.superset/worktrees/540f8918-…/test-setup, removed when the worktree was cleaned up).
    2. The app's persisted global state ~/Library/Application Support/ai.opencode.desktop/opencode.global.dat→server.projects.local[].worktreeandserver.lastProject.local`, both still pointing at the deleted path.

    What I observed:

    • At every app launch the sidecar calls FileSystem.realPath(lastProject) from inside Server.listen and dies with PlatformError: NotFound … ENOENT (stack in app.asar/out/main/chunks/node-*.js).
    • GET /api/reference?directory=<deleted> → HTTP 500.
    • Every POST /session/{id}/prompt_async for those sessions fails with Cause([Die(PlatformError: NotFound: FileSystem.realPath (…))]) — 200+ occurrences in ~/.local/share/opencode/log/opencode.log. As analyzed above, the Die bypasses the BadRequest mapping, so the client gets a 500 at best.
    • The chat UI shows no error at all: the message appears to send and the session just never responds. The error only lands in the app`'s notification store as a toast, nothing in-chat.
    • The project name displayed in the UI is derived from the git root (the main repo), so from the user`'s perspective they are "in the main project on the main worktree" while the session is actually bound to the deleted worktree subfolder — there is no way to see which worktree path a session is bound to.
    • The broken state persists across restarts with no recovery path in the UI; the only fix was removing the dead entry from opencode.global.dat / the stale sessions from the DB by hand.

    Building on the root-cause analysis above, I`'d suggest:

    • Validate projects[].worktree / lastProject at app boot and drop or flag missing paths before the sidecar resolves them.
    • When prompt_async dies this way, surface a session-level error in the chat UI (the event stream already carries enough to detect it).
    • Show the bound directory/worktree per session in the UI so stale bindings are visible.
  3. Hona commented on Sep 2, 2026

    @Hona
    Member

    Fixed for opencode2 launch via #46713
    You can try the desktop beta here: https://github.com/anomalyco/opencode-beta

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