Skip to content

bug: @mention subagent with attached image in Web UI — image not forwarded to multimodal subagent #25553

Closed as not planned

Description

@SXongin

Bug description

When using @vision (a subagent with multimodal model) in the Web UI and attaching/drag-dropping an image in the same message, the image is processed at the parent agent level instead of being forwarded to the subagent. Since the parent agent uses a non-multimodal model (e.g., DeepSeek V4 Pro), it errors with:

ERROR: Cannot read "image.png" (this model does not support image input)

The vision subagent never receives the image data.

Steps to reproduce

  1. Configure a multimodal subagent:
    # ~/.config/opencode/agents/vision.md
    ---
    description: Multimodal agent for image analysis
    mode: subagent
    model: xiaomi-token-plan-cn/mimo-v2-omni
    ---
  2. In Web UI, type @vision and drag-drop an image into the compose box
  3. Send the message
  4. Parent agent (non-multimodal model) receives the image and errors
  5. Subagent is spawned but never gets the image content

Expected behavior

When a user does @vision + attach image, the image data should be forwarded to the vision subagent's context, not processed at the parent agent level.

Environment

  • OpenCode version: 1.14.28
  • Interface: Web UI (opencode web)
  • Parent model: deepseek/deepseek-v4-pro
  • Subagent model: xiaomi-token-plan-cn/mimo-v2-omni
  • OS: Linux

Related issues

Activity

  1. github-actions commented on May 3, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Please review these issues to see if your report overlaps, and consider linking or closing if they address the same underlying problem.

  2. 21pounder commented on Jun 14, 2026

    @21pounder
    Contributor

    I reproduced this on the latest dev branch using bun dev serve.

    The main failing path is the Web UI style request shape: the user message contains text + agent + file, where @vision is sent as an agent part, not a subtask part.

    Reproduction

    1. Create a session

    POST http://localhost:4096/session
    Content-Type: application/json
    
    {
      "agentID": "build"
    }

    This returned session ses_13f04e5f9ffeU50f57Km5Zc5Yb.

    2. Send one message containing @vision and an image attachment

    POST http://localhost:4096/session/ses_13f04e5f9ffeU50f57Km5Zc5Yb/message
    Content-Type: application/json
    
    {
      "model": { "providerID": "deepseek", "modelID": "deepseek-v4-pro" },
      "parts": [
        {
          "type": "text",
          "text": "@vision describe this image"
        },
        {
          "type": "agent",
          "name": "vision",
          "source": { "value": "@vision", "start": 0, "end": 7 }
        },
        {
          "type": "file",
          "url": "data:image/png;base64,...",
          "mime": "image/png",
          "filename": "image.png"
        }
      ]
    }

    The request payload shows the expected text + agent + file shape:
    Image

    3. Inspect the stored session messages

    GET http://localhost:4096/session/ses_13f04e5f9ffeU50f57Km5Zc5Yb/message

    The stored message history shows that the parent session received the image together with the agent part:

    Image Image

    4. Inspect the server log for this request

    The relevant log block for the same request is:

    level=INFO  message=process  session.id=ses_13f04e5f9ffeU50f57Km5Zc5Yb  messageID=...
    level=INFO  message=stream   providerID=deepseek  modelID=deepseek-v4-pro
                               session.id=ses_13f04e5f9ffeU50f57Km5Zc5Yb
                               small=false  agent=build  mode=primary
    ...
    level=INFO  message="exiting loop"  session.id=ses_13f04e5f9ffeU50f57Km5Zc5Yb
    

    The log shows the parent session runs on deepseek-v4-pro, but there is no child session creation and no vision subagent execution in the same request path:

    Image Image Image Image

    What this confirms

    This reproduces the issue on the main path that users hit from the app:

    1. The user message contains an image attachment.
    2. The @vision mention is persisted as an agent part.
    3. The parent session processes the request with the primary model (deepseek-v4-pro in this repro).
    4. The log does not show a vision child session being created or streamed for this request.

    So the image is admitted into the parent message, but it does not make it into a multimodal vision subagent turn.

    Root cause

    There are two gaps in the current flow.

    First, the app builds the request as text + agent + file:

    • packages/app/src/components/prompt-input/build-request-parts.ts

    The backend then handles the agent part by appending synthetic text instructing the parent model to call the task tool:

    • packages/opencode/src/session/prompt.ts:957
    • packages/opencode/src/session/prompt.ts:968

    That means this path goes through the parent model first. It is not a direct subtask admission path.

    Second, TaskTool.execute only reconstructs child prompt parts from params.prompt:

    • packages/opencode/src/tool/task.ts:187

    resolvePromptParts(params.prompt) can resolve file references embedded in the prompt string, but it has no access to the already-admitted image file part from the parent user message. So even if the parent model does call the task tool, the attachment is not available to the child session unless it is explicitly threaded through.

    Fix approach

    I think the fix needs to cover the agent + file path directly, not only the direct subtask path.

    1. When a task is spawned from a user message that includes media attachments, the task execution path should forward those file parts to the child prompt explicitly instead of relying only on params.prompt.
    2. The forwarded attachments should be rebuilt as fresh child prompt parts rather than reusing persisted parent message part identities.
    3. This fix can stay scoped to the case where the subagent is actually spawned and only needs the attachment handoff to be preserved across that boundary.
  3. 21pounder commented on Jun 17, 2026

    @21pounder
    Contributor

    Hi @jlongster,

    I have submitted a PR (#32302) to address this issue.

    As detailed in my reproduction steps above, the root cause is that TaskTool.execute in packages/opencode/src/tool/task.ts only reconstructs child prompts from params.prompt, which completely leaves behind the original image/media file parts attached to the parent user message.

    The PR fixes this by explicitly forwarding parent media attachments to the spawned subagent's prompt context.

    Could you please take a look when you have a moment? Thank you!

  4. CSWNDQTJ-Kenny commented on Jul 28, 2026

    @CSWNDQTJ-Kenny

    Cross-linking the root cause analysis I posted on #34409 / #36020 / #32302:

    The image not being forwarded is because TaskTool.runTask builds the child subagent prompt only from params.prompt via resolvePromptParts, which never reads ctx.messages. So the file part attached to the parent user message is silently dropped before the subagent is spawned — regardless of Web UI vs CLI, and regardless of whether the parent LLM is multimodal.

    Minimal fix (matches #32302, which closes this issue but has been stalled since June):

    const parts6 = await ops.resolvePromptParts(params.prompt);
    const imageParts = (ctx.messages ?? [])
      .flatMap((m) => m.parts ?? [])
      .filter((p) => p.type === "file" && p.mime?.startsWith("image/"))
      .map((p) => ({ type: "file", mime: p.mime, url: p.url, filename: p.filename }));
    if (imageParts.length > 0) parts6.push(...imageParts);
    await ops.prompt({ parts: parts6 });

    A companion fix to unsupportedParts is also needed for config-defined OpenAI-compatible providers (doubao, kimi-vision, MiMo) — see #34409 comment for details. Both patches verified working on 1.17.20.

  5. github-actions commented on Sep 27, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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