Skip to content

cli: concurrent session.json writes race on Windows → EPERM, TUI loses current session #47497

Closed

Description

@rorshopping

Summary

Concurrent opencode2 CLI processes on Windows race on the TUI state file: the atomic rename of session.json.<pid>.<uuid>.tmp → session.json fails with EPERM, surfacing as an unhandled rejection (role=cli). The write is lost and the terminal no longer shows the current session.

Environment

  • opencode version: 0.0.0-beta-19151
  • OS: Microsoft Windows [Version 10.0.26200.9168]
  • Terminal: Windows Terminal (TERM=xterm-256color, WT_SESSION set)
  • Shell: C:\Windows\system32\cmd.exe (%ComSpec%); commands run via PowerShell 5.1
  • Install/channel: beta
  • Active plugins: @prevalentware/[email protected] (via cli.json plugins); local file plugin openrouter-retry-guard (~/.config/opencode/plugin/openrouter-retry-guard.ts); ~/.config/opencode/plugins/ empty; opencode.json lists no plugins

Reproduction

  1. On Windows, open an interactive opencode2 TUI (shared background service).
  2. Concurrently run repeated opencode2 run ... invocations against the shared service (e.g., an automation loop every ~25s; with ~8 concurrent clients it reproduces fast — 9 collisions observed within a single second).
  3. Watch ~/.local/share/opencode/log/opencode.log for unhandled rejection ... EPERM ... session.json.
  4. The TUI no longer shows the current session.

Expected Behavior

State writes from concurrent CLI processes should be serialized (lock file / retry with backoff on EPERM), so no write is lost, no unhandled rejection is logged, and the current session stays visible in the terminal.

Actual Behavior

The rename fails and the error escapes as an unhandled rejection; the session.json update is dropped and the current session disappears from the terminal. Exact error (paths normalized):

timestamp=2026-09-05T14:11:42.677Z level=ERROR run=9407f77e message="unhandled rejection" cause="Error: EPERM: operation not permitted, rename '~/.local/state/opencode/session.json.17072.9354ca59-7ab6-4dd3-9014-eb81bfe24c84.tmp' -> '~/.local/state/opencode/session.json'" role=cli

Same signature in bursts on 2026-08-19 (9 distinct PIDs, same second), 2026-08-20, and 2026-09-05. After the failure session.json still held the stale 13-byte content ({"pinned":[]}).

Additional Context

  • Intermittent, load-dependent: single interactive TUI alone rarely triggers it; concurrent run clients make it near-deterministic.
  • The shared background service stayed healthy throughout (same PID, /api/health 200, model catalog 200), isolating this to the CLI state-write path rather than the service.
  • Local workarounds that helped: routing single-shot automation through opencode2 run --standalone (no shared-state contact) and a pending Windows Defender exclusion for the state dir (needs admin; Add-MpPreference fails without elevation).
  • Happy to provide fuller redacted log excerpts on request.

Suggested Resolution

  • Serialize CLI state writes across processes: guard the read-modify-write of session.json with an interprocess lock (lockfile with PID + stale-lock expiry for crash safety).
  • Retry the tmp→final rename with backoff on EPERM/EACCES (Windows AV/indexer locks are usually transient): e.g. 3–5 attempts, ~50–200 ms jittered delay, then a single WARN — never an unhandled rejection.
  • A TUI-state persist failure must degrade to in-memory state, never escape as an unhandled rejection or drop the visible current session.
  • Longer term: single-writer design where the background service owns session.json and CLI processes send update RPCs instead of writing the file directly.
  • Regression test: spawn N concurrent writers against one state dir on Windows CI and assert no EPERM escapes and the final file parses.

Activity

  1. github-actions commented on Sep 5, 2026

    @github-actions
    Contributor

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

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions