Skip to content

service set/unset silently erases the stored config when service.json cannot be decoded #52412

Open

Description

@feiiiiii5

Description

ServiceConfig.read (packages/cli/src/services/service-config.ts) returns {} both when the file is missing and when it cannot be decoded:

return yield* fs.readFileString(configFile).pipe(
  Effect.flatMap(decodeInfo),
  Effect.orElseSucceed(() => ({}) as Info),
)

Every mutating path writes back { ...read(), changed }, so a config that fails to decode is treated as an empty one and immediately overwritten with only the field being changed. The command reports success.

Steps to reproduce

  1. Put a config file that cannot be decoded in place, e.g. one truncated mid-write:

    printf '{"hostname":"127.0.0.1","port":4321,"password":"user-secret","env":{"API_KEY":"sk-live-123"}}' > ~/.config/opencode/service.json
    printf '{"hostname":"127.0.0.1","password":"user-secr' > ~/.config/opencode/service.json
    
  2. opencode service set env NEW_VAR v — exits 0, prints nothing

  3. cat ~/.config/opencode/service.json → {"env":{"NEW_VAR":"v"}}

The stored hostname, port, password and env are gone, with no warning. opencode service get prints {} for the same file, and starting the managed service reaches the same read through ServiceConfig.password, which then issues a new credential instead of the one its own comment says it keeps across restarts.

A torn file is reachable without hand-editing: ServiceConfig.write publishes through a fixed service.json + ".tmp" with no lock, so two overlapping service set runs can interleave on that one path before the atomic rename. config/config.ts wraps its own config write in Flock.acquire, and services/service-registration.ts uses a unique temp name.

OpenCode version

v2, 5556ad563

Operating System

macOS

Activity

  1. github-actions commented on Oct 1, 2026

    @github-actions
    Contributor

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

    • #46128: reports silent data loss from unlocked whole-file JSON read-modify-write operations during concurrent writes, affecting auth.json rather than service.json.
  2. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report. I can reproduce this.

    Commit 5556ad563 isn't in a release, so I tested the latest release, @opencode/[email protected], using throwaway XDG directories:

    1. Wrote a truncated service.json containing hostname, port, password and env.
    2. opencode service get printed {}.
    3. opencode service set env NEW_VAR v exited 0 with no warning.
    4. The file now contained only {"env":{"NEW_VAR":"v"}}. Everything else was gone.

    It still happens on the latest dev (f0160a1) run from source. There, ServiceConfig.read still returns {} when the file can be read but neither the current nor the legacy format decodes it, and the write paths then overwrite the file. On dev, the temp file name is now per-process, which covers the fixed service.json.tmp collision you described. I did not test the concurrency part.

    The script below reproduces it.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces anomalyco/opencode#52412: an undecodable service.json is silently replaced.
    # Usage: ./repro.sh            (uses `opencode` from PATH)
    #        OC_DEV=/path/to/checkout ./repro.sh   (runs CLI from source)
    set -u
    WORK=$(mktemp -d)
    export XDG_CONFIG_HOME=$WORK/config XDG_STATE_HOME=$WORK/state XDG_DATA_HOME=$WORK/data XDG_CACHE_HOME=$WORK/cache
    mkdir -p "$XDG_CONFIG_HOME/opencode"
    oc() {
      if [ -n "${OC_DEV:-}" ]; then
        bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" "$OC_DEV/packages/cli/src/index.ts" "$@"
      else opencode "$@"; fi
    }
    echo "version: $(oc --version)"
    # Builds run from source use the "local" channel, whose file is service-local.json
    if [ -n "${OC_DEV:-}" ]; then CFG=$XDG_CONFIG_HOME/opencode/service-local.json; else CFG=$XDG_CONFIG_HOME/opencode/service.json; fi
    # A file truncated mid-write (fails JSON decoding)
    printf '{"hostname":"127.0.0.1","port":4321,"password":"user-secret","env":{"API_KEY":"sk-live-1' > "$CFG"
    echo "--- before:"; cat "$CFG"; echo
    echo "--- opencode service get:"; oc service get
    echo "--- opencode service set env NEW_VAR v:"; oc service set env NEW_VAR v; echo "exit=$?"
    echo "--- after:"; cat "$CFG"

    Run it: bash repro.sh

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions