Skip to content

Global config silently ignored when ~/.config/opencode/opencode.json is a symlink (stow/dotfile managers broken since ~v1.18.x) #39738

Description

@tomhuettmann

Description

When ~/.config/opencode/opencode.json is a symlink (e.g. managed via GNU Stow or similar dotfile managers), the config is silently ignored and opencode falls back to defaults. No error or warning is logged.

This worked correctly before the external_directory permission system was introduced (~v1.18.x). It appears the new permission system creates a chicken-and-egg problem: when loading the global config file, opencode resolves the symlink, detects the real path is outside ~/.config/opencode/, triggers external_directory authorization, but has no config loaded yet to evaluate the permission — so it silently discards the file.

Plugins

@mohak34/opencode-notifier@latest

OpenCode version

1.18.5

Steps to reproduce

  1. Place your opencode.json in a different directory, e.g. ~/dotfiles/opencode/.config/opencode/opencode.json
  2. Create a symlink: ln -sf ~/dotfiles/opencode/.config/opencode/opencode.json ~/.config/opencode/opencode.json
  3. Run: opencode debug config --print-logs
  4. Observe: logs show message=loading path=~/.config/opencode/opencode.json but the resolved config is empty (all defaults)
  5. Compare: OPENCODE_CONFIG=~/dotfiles/opencode/.config/opencode/opencode.json opencode debug config → config loads correctly

Screenshot and/or share link

No response

Operating System

macOS 26.6

Terminal

Terminal

Activity

  1. mohangk commented on Sep 20, 2026

    @mohangk

    Adding on to the symlink writing issue - ~/.config/opencode/opencode.json , I find that it also persists the resolved secret.

    On 1.18.31 (still reproduces on dev@83abc64): with "apiKey": "{env:MY_KEY}" in the global config, GET /global/config returns the substituted value, and PATCH /global/config writes its payload back over the file. A client that reads-then-saves therefore replaces the {env:...} token with the literal key, and the write swaps the symlink for a regular file (same atomic temp+rename path described here).

    Minimal repro, no client needed: start opencode serve, GET /global/config, PATCH that same body back, then grep apiKey ~/.config/opencode/opencode.json → literal secret.

    Relevant code: updateGlobal() in packages/opencode/src/config/config.ts merges the resolved payload over the file, and substitution runs in config/variable.ts before parsing.

    @tomhuettmann do you already have a PR for your original issue? I am happy to attach a failing test / small repro script if useful and also add on to your fix , if it makes sense

  2. tomhuettmann commented on Sep 28, 2026

    @tomhuettmann
    Author

    @mohangk Thank You! I did not work further on this and also do not have a PR. Feel free to take over :)

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