Whole-config rejection without last-good fallback kills all running sessions when a concurrent writer leaves a transient partial state in opencode.jsonc

Aperta
#62,872 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
48/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
json, typescript
Ambito
backend

Direzione di ricerca

Start with packages/core/src/config.ts, especially parseInfo and loadFile, and compare the well-known source fallback policy described there. Then inspect packages/util/src/fs-util.ts and the atomic temp-file rename pattern in packages/core/src/git.ts. Define done as ensuring transient partial writes cannot make active sessions lose the last valid configuration; the issue describes atomic writes or last-good fallback as acceptable approaches.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

reproduced
Description

When any concurrent writer leaves a transient partial state in the live global config file (~/.config/opencode/opencode.jsonc), a reloading opencode process fails to parse it and rejects the WHOLE configuration document - there is no fallback to the last-good configuration. The provider catalog then loses every model, and each running session dies at its next step with SessionRunnerModel.ModelUnavailableError ("Failed to drain Session"). Observed live on v2.2.5 with several concurrent opencode serve --stdio / ACP clients: 11 "rejected malformed JSON or JSONC document" normalization warnings, each followed within seconds by session-drain failures across three server processes.

Two contributing factors verified at tag v2.2.5:

  1. Whole-document rejection without last-good fallback: packages/core/src/config.ts (parseInfo/loadFile) drops the entry entirely when JSONC parsing fails. The codebase already follows the opposite policy for well-known sources - a failed refresh keeps the last resolved config "so remote providers and allowlists do not disappear" (config.ts, comment above the wellknown load path) - so extending that resilience to parse failures of the user's own config matches existing intent.
  2. In-place write of the live config: packages/core/src/config.ts line 482 persists via fs.writeWithDirs(filepath, ...), which resolves to a direct fs.writeFileString(path, content) without a temp-file + rename swap (packages/util/src/fs-util.ts, writeWithDirs). The codebase already uses an atomic temp+rename pattern elsewhere (for example packages/core/src/git.ts around line 967).

Reproduction notes: hammering PATCH /api/experimental/config 1200x against one server while another watched the same directory did NOT reproduce a partial read through opencode's own write path - its synchronous write completes before the watcher's next tick can observe intermediate bytes. However, any slower concurrent writer (editor autosave, scripts, plugin installs touching large configs) leaves the truncated-to-flushed window open long enough for readers to hit it, which matches how the live incidents occurred outside of controlled testing.

Expected behavior: readers of the live config never lose the entire configuration because of one transiently inconsistent state - either writes go through an atomic swap so partial states cannot be observed at all, or a parse failure falls back to the last-known-good document instead of dropping everything.

Plugins

none

OpenCode version

v2.2.5

Steps to reproduce
  1. Run two opencode server processes that watch the same global config directory.
  2. While sessions are active on both, have any third party rewrite the global config non-atomically and slowly enough that a reload lands inside the truncated-to-flushed window (editor-style save or chunked script write over a multi-kilobyte config).
  3. Observe "configuration normalization diagnostic ... rejected malformed JSON or JSONC document" in the log immediately followed by "Failed to drain Session" errors with ModelUnavailableError on all active sessions - no manual intervention recovers them until the file parses again.
Screenshot

n/a

Operating System

Ubuntu 24.04.5 LTS

Terminal

not applicable (ACP client)

Related Issues
  • https://github.com/anomalyco/opencode/issues/16450 - plugin config files deleted when multiple opencode instances run concurrently; different mechanism (concurrent bun install deleting files), closed without a fix. This report covers whole-document rejection on partial reads, which kills running sessions instead of losing files.
Lingua principale
TypeScript
Stelle
225k
Fork
30.4k
Merge medio
4h 16m
PR unite (30g)
528

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di anomalyco/opencode

Tutte le issue di anomalyco/opencode

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.