Skip to content

code mode: execute tool returns null for all code, including thrown errors #62923

Open

Description

@k1th

Summary

In OpenCode v2.2.6, the Code Mode execute tool returns null for every invocation — plain return values, console.log output, the global search() function, and even code that throws — while the MCP servers it is supposed to expose are healthy and connected. The result/error channel of the Code Mode runtime appears dead in this session.

Environment

  • opencode version: opencode v2.2.6 (desktop app /Applications/OpenCode.app 2.2.6; CLI at ~/Library/Application Support/ai.opencode.desktop/cli/2.2.6-arm64/opencode-cli)
  • OS: Darwin 27.2.0, macOS arm64
  • Terminal: OpenCode desktop app (Electron); spawned shells see TERM=xterm-256color
  • Shell: /bin/zsh
  • Install/channel: release 2.2.6 ("latest" channel, desktop install); server process runs as opencode-cli serve --service
  • Active plugins: none found (no plugin key in ~/.config/opencode/opencode.jsonc — only MCP server entries; no .opencode/ plugin dir in the working directory)
  • Working directory: a non-git directory (~/Documents/Default Project); server log shows worktree discovery failed ... Worktree.DirectoryUnavailableError for it

Reproduction

  1. Start a session in any directory (reproduced in a non-git project dir).
  2. Call the Code Mode execute tool with:
    async function main() { return "hello"; }
  3. Observe the tool result: null.
  4. Repeat with variations — all return null:
    • async function main() { console.log("x"); return { a: 1 }; }
    • async function main() { throw new Error("test error"); } (no error is surfaced)
    • async function main() { return await search({ query: "jira" }); } (global search also returns null)
    • async function main() { return await tools["<mcp-ns>"].someTool({...}); }
  5. Verify MCP health independently — all fine:
    • ps shows the npx-spawned MCP server processes running (jira / confluence / bitbucket / kubernetes MCP servers).
    • Server log (~/.local/share/opencode/log/opencode.log) shows mcp connected server=<jira-dc> tools=34, <bitbucket-dc> tools=21, <confluence-dc> tools=9.
    • Manual stdio JSON-RPC (initialize + tools/list) against the locally spawned JIRA MCP package answers with a valid protocol handshake and tool list.
    • A remote MCP endpoint (internal Jenkins MCP over HTTP) answers initialize correctly.
  6. Note: during the session, harness system messages repeatedly announced "The Code Mode tool catalog has changed. This catalog supersedes the previous Code Mode tool catalog." — the null behavior was identical before and after those catalog swaps.

Expected Behavior

execute runs the JavaScript in the confined Code Mode runtime and returns the final expression's value (or surfaces a thrown error), with tools.* bound to the connected MCP tool catalog and search() listing it.

Actual Behavior

Every execute call returns null. No result, no stdout, no error — thrown exceptions are swallowed into null as well, and search(...) inside execute also returns null. Reproducible consistently (10+ calls in one session), not intermittent.

Additional Context

  • Server log has no ERROR entries for tools/MCP during the session — only unrelated Failed to fetch models.dev timeouts and the worktree-discovery warnings for the non-git cwd.
  • A file under ~/.local/share/opencode/tool-output/ contains a successful MCP (JIRA) response payload, i.e. MCP tool calls can succeed server-side while the execute result channel still returns null to the model — pointing at the Code Mode result propagation, not at MCP connectivity.
  • Mid-session Code Mode catalog swaps ("catalog supersedes the previous catalog") are a plausible trigger; the harness, not the local config, changed.
  • Workaround used in the affected session: drive the MCP servers manually over stdio (npx + JSON-RPC messages) or call their backing REST APIs directly with the configured credentials; regular shell/ssh/file tools are unaffected.
  • Credentials, internal hostnames and tokens from the local MCP config were intentionally redacted from this report.

Activity

  1. opencodejr commented on Nov 9, 2026

    @opencodejr

    Thanks for the detailed report! I think this is a duplicate of #57484.

    In each of your examples the code declares async function main() { ... } but never calls it. execute treats code as a program body: the tool description says "Prefer an explicit return; if omitted, the final top-level expression becomes the result." A function declaration on its own produces no value, so the result is undefined, which is turned into null. Because main is never invoked, its return, console.log, throw, search(...) and tools.* calls never run, which is why the thrown error doesn't show up either. Your MCP servers and the catalog changes aren't the cause.

    These forms should work:

    return "hello"
    throw new Error("test error")
    async function main() { return "hello" }
    return await main()

    Possible duplicates and related issues, ordered by likelihood:

    The bot will close this issue as a duplicate in 1 day unless you reply explaining how your problem is different, for example if code written as a body with an explicit return, like return "hello", still returns null for you.

  2. k1th commented on Nov 9, 2026

    @k1th
    Author

    Thanks — retested and you're right, this was a usage error on my side, not a runtime bug.

    My repro snippets declared async function main() { ... } but never invoked it. Since execute treats code as a program body (final top-level expression = result), the program's result was the un-invoked function declaration → undefined → null, so nothing inside main ever ran. That matches #57484 exactly.

    Verified on v2.2.6 in this session with correctly formed code:

    • return "hello"; as the program body → returns "hello"
    • return await search({ query: "searchIssues", namespace: "atlassian-jira-dc" }) → returns the tool catalog
    • return await tools["atlassian-jira-dc"].jira_searchIssues({ jql: "key = EIS-289", maxResults: 1 }) → returns { success: true, data: { total: 1, issues: [ ... ] } }

    MCP servers were healthy throughout (processes running, mcp connected ... tools=34/21/9 in the server log, manual stdio JSON-RPC handshake fine). One note: the MCP tool results come back wrapped as { success, data }, which is easy to misread when indexing directly (e.g. r.issues[0] throws) — but that's just the response shape, not a bug.

    Happy to close as duplicate of #57484. The only mildly confusing part is that undefined → null is silent; the diagnostic hint proposed in #57484 ("program returned a function, invoke it") would have made this obvious immediately.

  3. opencodejr commented on Nov 9, 2026

    @opencodejr

    Thanks for retesting and confirming! Good to hear return ... bodies, search(...) and the Jira MCP calls all work on v2.2.6.

    We'll keep this as a duplicate of #57484, and the bot will close it as planned. Your point about the silent null is the same one raised in #57484; #50577 also asks for clearer Code Mode error messages. I can't promise any change there, though.

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