Skip to content

default_agent silently reverts to the built-in 'build' agent on a hot config reload (no restart) #62002

Closed as not planned

Description

@gandb

What happens

With a custom primary agent configured as default_agent, a session that is already open silently switches to the built-in build agent when the configuration is reloaded and one reload fails to read it. The custom agent's model and permissions are lost for that session, and a later valid reload does not restore them — only a restart does.

It is silent: the TUI status bar is the only visible hint.

Minimal reproduction (2.2.5, Linux)

5-line config, no plugins, no MCP, isolated HOME (I used a debian:12 container):

~/.config/opencode/opencode.json

{
  "model": "<provider>/<model>",
  "default_agent": "A",
  "agents": { "A": { "model": "<provider>/<model>", "mode": "primary" } }
}
# --standalone is required in a container: without it the CLI looks for the
# background service and exits with EXIT=1 (cli.server-connection.resolve).
podman run --rm -it --network host \
  -v /tmp/oc-home:/root \
  -v ~/.opencode/bin/opencode:/usr/local/bin/opencode:ro \
  debian:12 opencode --standalone

# NOTE: the write must happen INSIDE the container. Writing the file from the
# host through the bind mount does not trigger the watcher.
podman exec $CID sh -c 'printf "{ \"broken\": " > /root/.config/opencode/opencode.json'
# …then restore the valid file the same way

Observed

moment status bar
baseline A · <configured model>
config unreadable for a moment Build · <build model>
valid config restored Build · <build model> — does not come back

Two extra facts measured on the same binary:

  • changing default_agent in a valid config does not move an open session (default_agent: "build" left the session on its current agent);
  • removing/renaming the configured agent does move it, and restoring the config moves it back — so the selection is re-resolved only when the current agent stops existing.

Mechanism

packages/core/src/agent.ts → selectedDefault(): when data.default is unset, resolution order is configured → build → first selectable. A reload whose config cannot be read leaves the registry without the configured agent, so the session lands on build. Once there, build is a valid agent, so nothing forces re-resolution: later successful reloads re-apply the rest of the config (the model changes, measured) but not the agent.

Suggested behaviour

  • A reload that fails to read/parse the config should keep the previous agent selection and surface the failure, instead of falling back to a built-in.
  • default_agent should be (re)applied on a successful reload: an open session should follow the configured default, or at least recover once the config is valid again.

Environment

  • opencode 2.2.5 (npm @opencode/cli, repository anomalyco/opencode)
  • Linux (Linux Mint), Node 24.15.0
  • Also observed inside debian:12 with --standalone, so it is not host-specific

Activity

  1. gandb commented on Nov 5, 2026

    @gandb
    Author

    Controlled experiments (2.2.5) — corrected diagnosis

    I ran a probe matrix against a live session (tmux, outside any editor), reading the TUI status bar before/after each change. The original hypothesis in the report (the config transform not re-applying default_agent) is wrong; the reload path itself works. What actually breaks is the error path.

    probe (config change, no restart) session agent after
    touch opencode.json (mtime only) ORACLE (no change)
    content change in a plugin file ORACLE (no change)
    valid change: autoupdate toggled ORACLE (no change)
    newline added to agent/ORACLE.md ORACLE (no change)
    default_agent: "build" ORACLE (unchanged — an open session does not re-derive its agent from default_agent)
    agent renamed ORACLE → ORACLE_OFF switches to ORACLE_OFF
    config restored switches back to ORACLE
    opencode.json written INVALID for ~6 s, then restored to the ORIGINAL valid content Build — and a valid reload afterwards does NOT bring it back

    Minimal reproduction

    tmux new-session -d -s repro -c ~ 'opencode'
    sleep 6; tmux capture-pane -p -t repro | grep -E 'ORACLE|Build'
    #   ┃  ORACLE · <configured model>
    
    # a single broken read of the config, as any installer produces mid-copy:
    printf '{ "broken": ' > ~/.config/opencode/opencode.json
    sleep 6; tmux capture-pane -p -t repro | grep -E 'ORACLE|Build'
    #   ┃  Build · <build model>          <-- already switched
    
    # restore the exact original content:
    cp <backup> ~/.config/opencode/opencode.json
    sleep 7; tmux capture-pane -p -t repro | grep -E 'ORACLE|Build'
    #   ┃  Build · <build model>          <-- stays on Build until a full restart

    What this says

    1. One reload that cannot read the config leaves the agent registry without the configured agent, and selectedDefault() in packages/core/src/agent.ts degrades to build — silently.
    2. A later reload with a VALID config does not restore default_agent for an already-open session (the session's agent selection is not re-derived from the config, as the default_agent: "build" probe also shows).
    3. So the user-visible state after any install/partial write is: agent changed, model changed, and every permission scoped to the configured agent silently gone — until a restart.

    Suggested behaviour

    • A reload that fails to read/parse the config should keep the previous agent selection instead of re-resolving to a fallback, and should surface the failure.
    • default_agent should be (re)applied when the config reloads successfully — an open session should follow the configured default, or at least recover once the config is valid again.

    Both are observable in a single session and need no restart to verify.

  2. opencodejr commented on Nov 5, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report. I tried to reproduce this on 2.2.5 (Linux) and couldn't get the agent to switch.

    What I tried:

    • A global config with default_agent: "ORACLE" and an ORACLE agent that has its own model and a todo allow permission. Like yours, it had no mode.
    • Started the TUI, which showed ORACLE · …, and opened a session with a prompt.
    • Without restarting, added a plugin folder (plugins/todo-list/index.ts) to the config directory, then edited opencode.json. The logs show the plugins and config were reloaded both times.
    • I also tried a plugin that throws in setup().

    Each time the status bar still showed ORACLE, and the server still listed ORACLE as the default agent.

    These would help narrow it down:

    1. The todo-list plugin you copied, or a minimal version that still triggers the switch. What it registers (tools, agents, config transforms) may matter.
    2. Your full opencode.json with secrets removed, especially the provider setup for the ORACLE model, plus any project-level config in the directory where you start opencode.
    3. The log lines from the moment of the reload, from ~/.local/share/opencode/log/ (look for loading plugin, config.updated, agent.updated and any ERROR/WARN).
    4. Whether the switch happens on the home screen, in a session that already has messages, or both. Does it also happen if you only edit opencode.json, with no plugin change?

    Related issues, most likely first:

  3. gandb commented on Nov 5, 2026

    @gandb
    Author

    I rewrote the report — the original reproduction was wrong and I removed it.

    What changed

    • The reproduction no longer uses any plugin. It is a 5-line config with one custom agent and default_agent, run with an isolated HOME inside a debian:12 container (--standalone).
    • The hypothesis in the first version was wrong and is gone. I had blamed the config transform not re-applying default_agent; the transform is fine. The reload path works — what breaks is the reload that cannot read the config.
    • I added the two facts that pin the mechanism: changing default_agent in a valid config does not move an open session, and the selection is only re-resolved when the current agent stops existing.

    Why I updated it

    I ran a controlled probe matrix on a live session, reading the TUI status bar before/after each change, and the first version did not survive it:

    probe (no restart) agent after
    touch the config unchanged
    content change in a plugin file unchanged
    valid change: autoupdate toggled unchanged
    default_agent: "build" unchanged — an open session does not follow the default
    agent renamed away switches, and returns when restored
    config unreadable for ~6 s, then restored Build, and it stays there

    So the trigger is the failed read, not any config edit — which is exactly what an installer produces when it writes opencode.json in pieces. The plugin I originally included (todo-list) was never part of it; I only noticed it because that is what I happened to be installing, and its tools vanished afterwards as a side effect of the agent change.

    Two details from the isolated run that were not obvious on the host and may help whoever fixes this:

    • --standalone is required in a container (otherwise the CLI exits with EXIT=1 in cli.server-connection.resolve);
    • a write from the host through a bind mount does not trigger the watcher — the file has to be written from inside the same environment.

    Resulting behaviour is unchanged from the report: the session falls to build and a later valid reload re-applies the rest of the config (the model changes, measured) but does not bring the agent back.

  4. opencodejr commented on Nov 8, 2026

    @opencodejr
    Contributor

    Closing this for now. I couldn't reproduce it on 2.2.5: after adding a plugin folder and editing opencode.json without restarting, the configured default_agent (ORACLE) stayed selected, both in the TUI and on the server. I waited for the details asked for above, which haven't arrived yet: the plugin (or a minimal version of it), your config with secrets removed, the log lines from the reload, and whether it happens on the home screen or in a session with messages.

    If you can still reproduce it, please reply here with those details or open a new issue linking to this one, and we'll take another look. Thanks for the thorough report!

  5. gandb commented on Nov 12, 2026

    @gandb
    Author

    Closing the loop on this one, with a narrower reproduction than the original report.

    It is not pure opencode behaviour. Same machine, same config, same act, plain opencode in a tmux session (all ORACLE, never flips):

    • rm agents/ORACLE.md + touch opencode.json → unchanged
    • rm -rf agents/ → unchanged
    • agents/ deleted and opencode.json replaced (new inode) and the tree re-extracted → unchanged

    Inside an Orca terminal it does flip, opencode v2.2.7, Orca 1.4.255 and 1.4.256:

    moment status line
    config intact (default_agent: "ORACLE", agent defined only in ~/.config/opencode/agents/ORACLE.md) ORACLE · qwen3.8-27b-nvfp4 SGLang · medium
    after agents/ is deleted and the tree copied back Build · Big Pickle OpenCode

    The model falls back too, and restoring agents/ORACLE.md does not bring the agent back.

    The relevant difference: Orca does not run a self-contained opencode in that terminal — it starts a
    per-worktree opencode serve --service (ORCA_OPENCODE_PLUGIN_API=v2, agent-hook endpoint/token,
    ORCA_WORKTREE_ID) and the TUI attaches to it. The service pins the session's agent; when the config
    rewrite makes that agent momentarily nonexistent it falls back to build and never re-resolves
    default_agent — a plain TUI does re-resolve it.

    Filed upstream with the full evidence and steps: stablyai/orca#41275

    (For anyone reproducing: the trigger needs an agent defined only in agents/*.md. A config that
    defines the agent inline in opencode.json will not show it, because "agents" — plural — is not a
    v2 config key.)

  6. opencodejr commented on Nov 12, 2026

    @opencodejr
    Contributor

    Thanks for following up with the narrower steps and for filing it with Orca.

    I tried them on 2.2.7 with the TUI attached to an opencode serve --service process, which is the same layout Orca uses, but without Orca's plugin or environment:

    • default_agent: "ORACLE", with ORACLE defined only in agents/ORACLE.md and given its own model
    • opened a session, then deleted agents/
    • copied agents/ORACLE.md back and replaced opencode.json with a new file

    While agents/ was gone, the status bar showed Build. That's expected, because the agent didn't exist at that moment. As soon as the file was back, it returned to ORACLE · …, and it stayed that way after the next prompt and in a new session. So with plain OpenCode, even in service mode, the configured default came back for me. This matches what you saw outside Orca.

    That leaves the part that doesn't recover inside Orca's setup, which looks like it belongs in stablyai/orca#41275. If it turns out to need a change in OpenCode, for example you can trigger it with opencode serve --service and a plain attached TUI, without Orca's plugin, please reply here with those steps and we'll take another look.

    Related: #49982, where a running service loses file-discovered agents until it restarts.

  7. opencodejr commented on Nov 12, 2026

    @opencodejr
    Contributor

    Closing this one. On 2.2.7, with the TUI attached to opencode serve --service, the configured default_agent came back as soon as agents/ORACLE.md was restored. The agent only stayed on Build inside Orca's terminal, which is now tracked in stablyai/orca#41275. We waited in case it also happened without Orca's plugin and environment, and nothing like that has come up.

    If you find a way to trigger it with plain OpenCode (for example opencode serve --service with an attached TUI and no Orca plugin), please open a new issue with those steps and link this one. Thanks for digging into it so thoroughly!

  8. gandb commented on Nov 12, 2026

    @gandb
    Author

    Reopening — this is not Orca-specific. It also happens with plain opencode in a normal terminal: MATE Terminal (VTE 0.76.0, TERM=xterm-256color, COLORTERM=truecolor, TERM_PROGRAM unset), no Orca window in that session's chain.

    Environment

    • opencode 2.2.7 (opencode --version), Linux Mint.
    • default_agent: "ORACLE", and the agent is defined inline in ~/.config/opencode/opencode.json inside the agents block — there is no ~/.config/opencode/agent/ORACLE.md in this setup. The workaround from the closing comment ("the configured default_agent came back as soon as agents/ORACLE.md was restored") therefore does not apply here: there is nothing to restore, and the session still cannot select the agent.

    What the server logs — 9 times today (2026-11-12), each within seconds of a failed reload

    level=ERROR message="failed to reload plugins" cause="Cause([Die(TypeError: o is not a function. (In 'o()', 'o' is an instance of Object))])"
    level=ERROR message="Failed to drain Session"
      cause="Session.AgentNotFoundError: Agent not found: \"ORACLE\""
        at SessionContext.select
        at SessionRunner.prepareContext
        at SessionRunner.advanceToStep
        at SessionRunner.drain
        at ServerProcess.start
    

    Reload → failure pairs (local time, same machine):

    reload event AgentNotFoundError
    10:21:43 failed to reload plugins 10:21:43 · 10:23:57
    10:59:34 failed to reload plugins 10:59:36 · 10:59:59 · 11:00:35
    11:04:21 failed to reload plugins 11:04:22
    11:40:27 plugin reconciliation 11:40:27 · 11:41:17
    11:47:57 failed to reload plugins 11:48:16

    A second primary agent defined the same way (ORCHESTRATOR) fails identically at 10:59:59, so this is not one agent name.

    Meanwhile the TUI keeps showing the configured agent in the status bar (ORACLE · <model>): the client believes the session is on ORACLE while the server refuses to select it, and the turn is dropped with Failed to drain Session.

    Expected

    • A reload that fails (plugins or config) keeps the previous agent registry instead of leaving it without the configured agents.
    • A session whose configured agent is still present in the config never fails with AgentNotFoundError.
    • If agents defined inline in opencode.json are dropped by the reload path (they are not files under agent/, so nothing re-reads them), that is a concrete way to reproduce this without Orca.

    Screenshot: MATE Terminal window showing Error: Agent not found: "ORACLE" in the transcript while the status bar below reads ORACLE · DeepSeek V4.1 Flash 1017, attached in the next comment.

  9. gandb commented on Nov 12, 2026

    @gandb
    Author

    Filed the same failure with a terminal-only reproduction (MATE Terminal, VTE 0.76, opencode 2.2.7, agents defined inline in opencode.json): #63528. Screenshots are there — reopening this issue from the reporting account is refused by GitHub (GraphQL: Could not reopen the issue. (reopenIssue), REST 422).

  10. gandb commented on Nov 13, 2026

    @gandb
    Author

    Re-tested every scenario from this thread on v2.3.0 (tag a6b15d33, which contains ec263d62b6 / #63548). All of them are resolved. Scenario by scenario:

    1. Probe matrix — valid reloads never moved the session (2.2.5)

    touch opencode.json · content change in a plugin file · autoupdate toggled · newline added to agent/ORACLE.md → the session agent stayed ORACLE in every case. That part was always correct, and it still is on 2.3.0: no regression, nothing to fix.

    2. The error path — an unreadable config for ~6s flipped the agent to Build, and a later valid reload did not bring it back

    Fixed. On 2.3.0 a broken config no longer leaves the service without its agents: once a valid config is written back, the agent is there — no restart, and the previous "stays on Build until restart" behaviour is gone.

    3. Plain opencode in tmux — never flipped (2.2.7)

    Unchanged on 2.3.0, as expected. This never was the failing path.

    4. Inside an Orca terminal it flipped (2.2.7, Orca 1.4.255 and 1.4.256 — Orca runs opencode serve --service and the TUI attaches; the model fell back too)

    Fixed on 2.3.0. Same config, same act: the agent and the model survive the reload, no restart needed.

    5. Plain terminal, no Orca at all — MATE Terminal (VTE 0.76.0), agent defined inline in the agents block of opencode.json

    This was the case the triage workaround did not cover (there is no agent/ORACLE.md to restore). The log pair was:

    failed to reload plugins … o is not a function
    Session.AgentNotFoundError: Agent not found: "ORACLE"
    

    Fixed on 2.3.0: the o is not a function teardown no longer aborts the plugin swap, so the next generation of agents/commands is loaded and the session keeps running. No AgentNotFoundError in the log, and inline agents behave exactly like file-defined ones.

    6. #63528 (same failure, terminal-only reproduction)

    Same root cause, same fix — it can be considered resolved by 2.3.0 as well.

    So: every scenario I filed is now closed from my side. Thanks for the fix — the failing-finalizer path was the real cause all along, and it is good to see it shipped rather than patched locally.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions