You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin: an unreadable package.json in one local plugin directory removes every plugin and provider (2.2.0) #59750
On 2.2.0, one local plugin directory can have a package.json that the service cannot read (for example, mode 000). Then the service loads no plugins and no providers. /api/plugin and /api/provider give an empty list. This occurs at first boot and after a Location reload. On 2.1.4 the same setup is correct: the two plugins are active.
existsSync gives true, then readFileSync throws EACCES, and no code catches the error. The comment at :86 says that one broken plugin directory must not stop the other plugins. The service log has:
level=ERROR message="failed to reload plugins" cause="Cause([Die(Error: EACCES: permission denied, open '<project>/bad/package.json')])"
Steps to reproduce
Make a project with two local plugin directories, good and bad. Each has an index.ts that exports { id, setup }, and bad also has a package.json.
Start opencode serve --service and send GET /api/plugin and GET /api/provider with the x-opencode-directory header. For the reload test, start with a readable file, change the mode, then send POST /api/location/reload.
I used isolated HOME and XDG roots, one OpenAI-compatible test provider, and no model request.
Version
Case
/api/plugin
/api/provider
2.1.4
first boot, mode 000
92 records, good and bad active
1
2.1.4
after reload, mode 000
92 records, good and bad active
1
2.2.0
readable package.json
92 records, good and bad active
1
2.2.0
first boot, mode 000
0
0
2.2.0
after reload, mode 000
0
0
Possible repair
In packageManifest, catch the read error and use the same result as a missing file, as the comment says. A warning for that one plugin can also help.
Environment
OpenCode 2.2.0 and 2.1.4 release binaries, Linux x86-64.
Thanks for the detailed report. I can confirm this on 2.2.0, and it still happens on the latest dev (5aeac4f).
What I did, following your steps (two local plugins good and bad, plus one OpenAI-compatible provider, opencode serve --service):
Readable bad/package.json:/api/plugin returns 92 records with both local plugins active, and /api/provider lists the providers.
chmod 000 bad/package.json, run as a non-root user: both lists are empty on first boot. The log shows failed to reload plugins with Die(Error: EACCES: permission denied, open '.../bad/package.json').
package.json replaced by a directory: same result, with EISDIR. As in your case, the existence check passes and the read throws.
Reload: with a readable file it boots fine. After the file is made unreadable, POST /api/location/reload empties both lists.
On dev the reload case fails the same way. The function you pointed at, packageManifest in packages/plugin/src/host.ts, hasn't changed since v2.2.0. The script below reproduces it (readable, eacces, eisdir and reload modes).
Related, but with a different cause: #52618 and #49982, where a failed plugin reload also wipes the provider or agent registries.
Reproduction script (repro.sh)
#!/usr/bin/env bash# Reproduces #59750: one local plugin whose package.json cannot be read empties /api/plugin and /api/provider.# Usage: repro.sh [readable|eacces|eisdir|reload] (OC="<command>" to override the opencode binary)# eacces needs a non-root user (root ignores mode 000); eisdir makes package.json a directory, which also# passes existsSync and makes readFileSync throw.set -u
MODE=${1:-eacces}
OC=${OC:-opencode}
mkdir -p /tmp/opencode
ROOT=$(mktemp -d /tmp/opencode/repro59750.XXXX)
P=$ROOT/project; mkdir -p $P/good $P/bad $ROOT/home
fornin good bad;do
cat >$P/$n/index.ts <<TSexport default { id: "$n-plugin", setup: async () => {} }TSdoneecho'{"name":"bad-plugin","type":"module"}'>$P/bad/package.json
cat >$P/opencode.json <<'J'{ "plugins": [{ "package": "./good" }, { "package": "./bad" }], "provider": { "mock": { "npm": "@ai-sdk/openai-compatible", "name": "Mock", "options": { "baseURL": "http://127.0.0.1:9/v1", "apiKey": "x" }, "models": { "m": { "name": "M" } } } } }Jcase$MODEin
eacces) chmod 000 $P/bad/package.json ;;
eisdir) rm $P/bad/package.json; mkdir $P/bad/package.json ;;
esacexport HOME=$ROOT/home XDG_CONFIG_HOME=$ROOT/home/.config XDG_DATA_HOME=$ROOT/home/.local/share \
XDG_STATE_HOME=$ROOT/home/.local/state XDG_CACHE_HOME=$ROOT/home/.cache
mkdir -p $XDG_CONFIG_HOME/opencode;forfin opencode/service.json opencode/service-local.json;doecho"{\"password\":\"repro\"}">$XDG_CONFIG_HOME/$f;done
PORT=$((20000+ RANDOM %20000))cd$P$OC serve --service --port $PORT --hostname 127.0.0.1 >$ROOT/serve.log 2>&1&
PID=$!foriin$(seq 60);do curl -s -o /dev/null http://127.0.0.1:$PORT/ &&break; sleep 0.5;done
H="x-opencode-directory: $P"api() { curl -s -u opencode:repro -H "$H""$@"; }
report() {
# plugins load in the background; wait up to 30s for the list to settleforiin$(seq 30);do n=$(api http://127.0.0.1:$PORT/api/plugin | jq '.data|length'); [ "$n"!="0" ] &&break; sleep 1;doneecho" /api/plugin: $(api http://127.0.0.1:$PORT/api/plugin | jq -c '"\(.data|length) records, local: \([.data[]|select(.source.type=="local")|"\(.id)=\(.state.status)"])"')"echo" /api/provider: $(api http://127.0.0.1:$PORT/api/provider | jq -c '[.data[].id]')"
}
echo"version=$($OC --version) mode=$MODE"echo"first boot:"; report
if [ "$MODE"="reload" ];then
chmod 000 $P/bad/package.json 2>/dev/null
[ "$(id -u)"= 0 ] && { rm $P/bad/package.json; mkdir $P/bad/package.json; } # root ignores mode 000
api -X POST http://127.0.0.1:$PORT/api/location/reload -o /dev/null -w "reload HTTP %{http_code}\n"
sleep 3;echo"after reload with unreadable package.json:"; report
fi
[ -n"${KEEP:-}" ] && { echo"ROOT=$ROOT PORT=$PORT";exit; }
kill$PID;wait$PID2>/dev/null
grep -rh "failed to reload plugins\|EACCES\|EISDIR"$ROOT/serve.log $ROOT/home 2>/dev/null | head -3 | cut -c1-300
Activity
opencodejr commentedon Oct 27, 2026
Thanks for the detailed report. I can confirm this on 2.2.0, and it still happens on the latest
dev(5aeac4f).What I did, following your steps (two local plugins
goodandbad, plus one OpenAI-compatible provider,opencode serve --service):bad/package.json:/api/pluginreturns 92 records with both local plugins active, and/api/providerlists the providers.chmod 000 bad/package.json, run as a non-root user: both lists are empty on first boot. The log showsfailed to reload pluginswithDie(Error: EACCES: permission denied, open '.../bad/package.json').package.jsonreplaced by a directory: same result, withEISDIR. As in your case, the existence check passes and the read throws.POST /api/location/reloadempties both lists.On
devthe reload case fails the same way. The function you pointed at,packageManifestinpackages/plugin/src/host.ts, hasn't changed sincev2.2.0. The script below reproduces it (readable,eacces,eisdirandreloadmodes).Related, but with a different cause: #52618 and #49982, where a failed plugin reload also wipes the provider or agent registries.
Reproduction script (repro.sh)
Run it:
bash repro.shAafimalek commentedon Oct 28, 2026
I would like to investigate this and add regression coverage for an unreadable local plugin manifest. Could you assign it to me?