Skip to content

Agent not found for an existing agent while plugins reload after a plugin file changes #54172

Closed

Description

@remorses

Description

A session fails with Agent not found: "opus" while opus exists (~/.config/opencode/agents/opus.md). This happens when a configured plugin file changes in the middle of a turn.

The plugin watcher reloads plugins and config in all locations. During the reload, the agent catalog is built again without the agents from config. If the next step starts in that window, SessionContext.select (session/context.ts:129) throws AgentNotFoundError and the execution fails. About 50 ms later, agent.updated fires and the agent is available again.

The agent was not renamed or removed. This is different from #53500. I think the cause is that the reload is not atomic: the old config transform is removed before the new one is registered. Related: #52618.

Log, same ms window:

16:26:37.329Z loading plugin ... (x4 locations)
16:26:37.640Z watcher stopped/started ... AGENTS.md, skills
16:26:37.649Z ERROR Failed to drain Session cause="Session.AgentNotFoundError: Agent not found: \"opus\"
    at SessionContext.select ... SessionRunner.prepareContext ... SessionRunner.advanceToStep"
16:26:37.704Z event.type=agent.updated

It happened twice in the same session (16:26:37, 16:28:08). Both times the previous tool call ran tsc, which rewrites the plugin entrypoint.

Expected: an existing agent always resolves during a reload, or select waits for the reload to finish.

Plugins

local plugin dir in opencode.json plugins (.../dist/plugin/anthropic), plus files in ~/.config/opencode/plugins/

OpenCode version

2.0.19

Steps to reproduce

  1. Add a local plugin directory to plugins in the global opencode.json.
  2. Use a custom agent from ~/.config/opencode/agents/*.md.
  3. In a session, make the agent run a shell command that rewrites the plugin entrypoint (for example tsc that builds the plugin), so that the next step starts as the reload begins.
  4. The execution fails with Agent not found: "<agent>".

Screenshot and/or share link

No response

Operating System

macOS

Terminal

opencode serve (service), driven through the HTTP API

Activity

  1. opencode-agent commented on Oct 9, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I could reproduce the same failure on 2.0.19, and it still happens on the latest v2 (229b422).

    Setup: a custom primary agent (opus, set as the default agent), a local plugin file in the config's plugins folder, and a simulated model. Every step, the model ran a shell command that rewrote the plugin file and then slept a little longer each round, so the next step would start at different points while the plugins reload. After about 20 rewrites the turn stopped silently, with no error in the TUI, and the server log showed:

    Failed to drain Session cause="SessionRunnerModel.ModelUnavailableError: Model unavailable: simulation/gpt-sim-model"
    

    In my run the missing entry was the model from the config's providers, not the agent, but the cause looks the same: what comes from config is briefly missing while plugins reload, and a step that starts in that window fails. The recording and script below show it.

    Related: #53500 (same drain failure, with a renamed or removed agent), #52618 and #49982 (plugin reloads dropping registry entries).

    recording.mp4
    Reproduction script (drive.ts)
    // Repro for anomalyco/opencode#54172: rewriting a watched plugin file mid-turn
    // makes the next step fail with `Agent not found: "opus"` for an existing agent.
    import { Effect, Stream } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    const ROUNDS = 50
    const PLUGIN = (rev: string) =>
      `import fs from "fs"\nexport default { id: "watched", async setup() { fs.appendFileSync("/tmp/opencode/plugin-loads.log", new Date().toISOString() + " ${rev}\\n") } }\n`
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
        project: {
          git: true,
          files: {
            ".opencode/agents/opus.md": "---\ndescription: Custom agent\nmode: primary\n---\nYou are opus.\n",
            ".opencode/plugins/watched.js": PLUGIN("init"),
          },
        },
        config: { default_agent: "opus" },
      },
      ({ ui, llm }) =>
        Effect.gen(function* () {
          // Every model request answers with a shell call that rewrites the plugin file,
          // so the next step starts right as the plugin watcher reloads.
          yield* llm.serve((_req, index) =>
            index < ROUNDS
              ? Stream.make(
                  Llm.toolCall({
                    index: 0,
                    id: `call_${index}`,
                    name: "shell",
                    input: {
                      command: `cat > .opencode/plugins/watched.js <<'EOF'\n${PLUGIN(String(index))}EOF\nsleep ${(0.05 + index * 0.006).toFixed(2)}`,
                      description: "rebuild plugin",
                    },
                  }),
                  Llm.finish("tool-calls"),
                )
              : Stream.make(Llm.text("All rounds done."), Llm.finish()),
          )
          yield* ui.submit("Rebuild the plugin repeatedly")
          const result = yield* Effect.raceFirst(
            ui.waitFor("Agent not found", { timeout: 120_000 }).pipe(Effect.as("agent-not-found")),
            ui.waitFor("All rounds done.", { timeout: 120_000 }).pipe(Effect.as("completed")),
          ).pipe(Effect.catch(() => Effect.succeed("timeout")))
          yield* Effect.sleep("1500 millis")
          // Show OpenCode's own log lines about the failure (isolated data dir under the system temp dir).
          const logs = Bun.spawnSync(["sh", "-c", "grep -rh -E 'Failed to drain|AgentNotFound|failed to reload' $(find / -xdev -path /proc -prune -o -name '*.log' -path '*opencode/log*' -mmin -10 -print 2>/dev/null) | cut -c1-600 | tail -5"])
          yield* Effect.log(`LOGS:\n${logs.stdout.toString()}`)
          yield* ui.screenshot("result")
          yield* Effect.log(`RESULT: ${result}`)
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. argszero commented on Oct 10, 2026

    @argszero
    Contributor

    I'd like to work on this.

    Confirming the mechanism against v2: packages/core/src/session/context.ts:129 is exactly the line in the report, and the failure is that the config/plugin reload is not atomic. The reload tears down the current config-derived registrations (including the agent transform) and only then registers the replacements, so any step whose SessionContext.select runs inside that window observes an agent catalog with the config-derived agents missing and throws Session.AgentNotFoundError — even though the agent file was never touched and is back ~50 ms later.

    A fix has to make an agent that exists both before and after a reload keep resolving for the whole duration of the reload. The two candidate shapes:

    1. Publish-then-retire: build the new registration set and swap it in before the previous one is dropped, instead of remove-then-add.
    2. Make select tolerate an in-flight reload (wait for it to settle rather than read a half-built catalog).

    I'll decide between them after reading the reload path (packages/core/src/config.ts, packages/core/src/config/plugin/entry-observer.ts, packages/core/src/plugin/module.ts) in full, and will validate with a regression test that forces a reload between two steps of the same session.

    There is no open PR for this, and per the #52618 cross-reference this may share a root cause with that report — if one fix covers both I'll say so in the PR rather than duplicating.

    Branch will be based on v2.

  3. nicko917 commented on Oct 12, 2026

    @nicko917

    Repro confirmado en v2.0.27 (Linux), matching this issue:

    Setup: one custom plugin loaded from a watched file (plugins: ["/path/to/repo/src"]), plus 13 custom agents in ~/.config/opencode/agents/*.md. Long-running session + background subagent sessions.

    Repro:

    1. Edit the plugin's source file repeatedly (subagent doing implementation work) → file watcher triggers msg="loading plugin" reload bursts (observed 6 loads per burst in logs).
    2. Shortly after the reload bursts, all agent lookups fail permanently: Session.AgentNotFoundError: Agent not found: "<id>" in SessionRunner.prepareContext → SessionContext.select, for custom agents AND for the session's own configured default_agent.
    3. GET /api/agent returns [] (empty) — the agent registry stays wiped for hours, not just during the reload window. Subagent spawns fail immediately; queued steps of the parent session also fail to drain (same error).

    Workaround: opencode service restart restores discovery (/api/agent populated again) until the next plugin-reload burst.

    Note: our earlier diagnosis misread this as a transient race (2 failures, 2 successes, 3 failures) — the successes were all BEFORE the first reload burst; the registry was silently empty from that point on. Suggestion: make agent discovery re-run after plugin reload completes, or never clear the registry on reload failure.

  4. AlexeyShirshov commented on Oct 14, 2026

    @AlexeyShirshov

    Same failure on 2.0.28, but here the reload was triggered by a write to the config file, not by a
    plugin file change — so the non-atomic window is hit by any writer to watched paths
    (opencode.jsonc/cli.json, plugins, AGENTS.md, skills), including a deploy step run from inside an
    agent session.

    Environment

    • opencode 2.0.28 (channel latest), Linux WSL2 (6.18.33.2-microsoft-standard-WSL2), x64 — TUI client (opencode -c) against the shared background service
    • 14 plugins active: local plugins/*.js (roslyn.js, btw-context.js, windows-notify.js, …) + superpowers@git+… (loaded by the CLI, reconciled per client action)
    • 42 agents resolve for the location: ~/.config/opencode/agents/*.md plus 7 project agents in <project>/.opencode/agents/*.md

    Timeline (one window, UTC)

    06:17:04.526  bash in an agent session (config deploy): cp config/opencode.jsonc config/cli.json ~/.config/opencode/
    06:17:04.949  event.type=config.updated
    06:17:04.999  event.type=config.updated
    06:17:05.008  event.type=config.updated
    06:17:04.5–06   watcher stopped: all skills*/ dirs, <project>/AGENTS.md        ← location teardown
    06:17:04.780  loading plugin ×N (roslyn.js, btw-context.js, superpowers)      ← rebuild
    06:17:06.071  ERROR "Failed to drain Session" cause="Session.AgentNotFoundError: Agent not found: \"coder\""
    06:17:06.129  ERROR "Failed to drain Session" cause="Session.AgentNotFoundError: Agent not found: \"pdca-orchestrator\""
    06:17:06.090  watcher subscribe → registry built again
    

    Three things that may be new relative to this report

    1. Both the dispatch target and the session's own agent die in the same window. The drain first failed to
      resolve coder (the subagent it was dispatching), 58 ms later pdca-orchestrator (the session's own
      agent). So the race kills parent sessions, not only the child dispatch.
    2. Both ids exist immediately before and after. Right after the window,
      GET /api/agent?location[directory=…] returns 42 agents including coder and pdca-orchestrator, and
      agent.updated follows the reload flood. session_inbox/session_pending were empty (nothing queued),
      yet the session stays dead.
    3. User-visible outcome (local session ids): ses_ec6feec99ffeaiWono4wVnuSUd (agent pdca-orchestrator)
      ends with an idle record
      {"outcome":"failed","error":{"type":"unknown","message":"Agent not found: \"pdca-orchestrator\""}},
      and the PDCA cycle had to be restarted in a fresh session ses_ec6f11093ffe6La3zQyvp10a6B. That is the
      Completed session marked failed, work reported lost, when its agent is missing at drain (Session.AgentNotFoundError) #53500 "completed session marked failed / work reported lost" symptom with the same-window cause.

    Minimal repro without touching a plugin

    Keep a session draining (long turn or subagent dispatch), then write any watched config file
    (cp opencode.jsonc ~/.config/opencode/). The drain fails with AgentNotFoundError for an agent that
    exists.

    Related: #53500 (session marked failed / work lost), #52618 (reload wipes provider registry),
    #48121 (concurrent location reloads), #49740 (drain failures never surfaced).

  5. rebelliard commented on Oct 15, 2026

    @rebelliard

    👍🏽 Deterministic repro on 2.0.27 (macOS): hold the reload window open with a slow plugin.

    • .opencode/opencode.json defines one agent, probe ("mode": "all").
    • .opencode/plugins/probe/index.ts: export default { id: "repro.probe", async setup() { await new Promise((r) => setTimeout(r, 30000)); } };
    • Prompt a probe session: "Run the shell command sleep 10 once, then reply OK." About 4 s later, edit a comment in the plugin file.
    • When the tool returns, the next step starts inside the 30 s reload and fails: Session.AgentNotFoundError: Agent not found: "probe" (2 of 2 runs). With no edit, the same prompt succeeds.

    A new prompt sent during the reload is held until agent.updated, so only the next step of a session that is already running fails.

    The fix is up as #51133 (the same wait at the top of prepareContext, plus manual compaction). GitHub reported it as conflicting with dev on 2026-10-14. A regression scenario that fails on dev and passes with the wait is in ba56d9a.

  6. Tarquinen commented on Oct 24, 2026

    @Tarquinen
    Member

    Thanks for the report! #51133 makes each step and tool call wait for plugin reloads to finish, so agents, models and skills from config no longer go missing mid-turn. It will be in the next release. If you still see this on the latest OpenCode, or agents stay missing after a reload has finished (like the empty /api/agent reported above), please open a new issue with details. Closing as completed.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

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