Skip to content

app: showing a local image registers its folder as a project #58762

Open

Description

@lsequeiraa

Description

When the web app shows a local image given by absolute path (a read preview, or an image in markdown, which uses the same loader), it fetches it with the image's parent folder as the location (readLocalImage in packages/app/src/runtime/server/image.ts: "Scope absolute images to their parent, including files outside the project"). Naming a location makes the server resolve and persist it. So if that folder isn't inside a known project, it becomes a new project at the top of the project list, and its location services boot: config, plugins, file watchers, and a connection to every configured MCP server, all for that folder.

Opening one preview of a render outside any repository (/mnt/d/Projects/vrchat/tripo/bounty/eval/views.png) did exactly that: a new project for …/bounty/eval/, and the log shows location services booted for it followed by three mcp connected lines. Two other image folders had become projects earlier the same day, booted with the same trailing-slash location readLocalImage produces. The projects stay: there is no API to archive or delete one, and the hourly sweep only archives projects whose directory is gone.

Possible fixes: resolve the preview's location without persisting it (Project.resolve takes persist: false since 2.1.4), or read images inside the session's directory with the session's location, as relative paths already are.

Plugins

None relevant

OpenCode version

2.1.4 (image.ts unchanged on dev as of 20a6cc7)

Steps to reproduce

  1. Have the agent read a PNG by absolute path in a folder that isn't inside any project.
  2. In the web app, expand that read row to show the preview.
  3. GET /api/project: the folder is now a project, and the server log shows its location services booting and each MCP server connecting for it.

Screenshot

No response

Operating System

Linux (opencode service on WSL2 Ubuntu 24.04); web app on Chrome

Terminal

N/A (web app)

Activity

  1. opencode-agent commented on Oct 24, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I can confirm this on 2.1.4, and it still happens on the latest dev (2dc8db4).

    The script below runs opencode serve with a stub MCP server and sends the request readLocalImage makes for an absolute image path: GET /api/fs/read/views.png?location[directory]=<image folder>/. The image comes back fine, but then:

    • GET /api/project lists the image folder as a new project;
    • the server log shows location services booted for that folder (with the trailing slash), plus a file watcher on it;
    • the MCP server starts a second time, with the image folder as its working directory.

    The server-side location resolution saves any directory it's given as a project. A maintainer will take a look.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces anomalyco/opencode#58762 against the server API, sending exactly the
    # request the web app's readLocalImage makes for an absolute image path.
    # Usage: ./repro.sh                -> uses `opencode` from PATH
    #        OC_DEV=/path/to/repo ./repro.sh -> runs the CLI from a source checkout
    set -u
    ROOT=$(mktemp -d /tmp/opencode/r58762.XXXX)
    export 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" "$ROOT/project" "$ROOT/outside/eval"
    # A fake stdio MCP server that just records each launch (with its cwd) and stays alive.
    cat > "$ROOT/fake-mcp.sh" <<MCP
    #!/usr/bin/env bash
    echo "launched cwd=\$PWD" >> "$ROOT/mcp-launches.log"
    exec sleep 600
    MCP
    chmod +x "$ROOT/fake-mcp.sh"
    cat > "$XDG_CONFIG_HOME/opencode/opencode.json" <<CFG
    { "mcp": { "fake": { "type": "local", "command": ["$ROOT/fake-mcp.sh"] } } }
    CFG
    (cd "$ROOT/project" && git init -q && git -c user.email=a@b -c user.name=a commit -q --allow-empty -m init)
    # 1x1 PNG outside any project
    printf '\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\0\x01\0\0\0\x01\x08\x06\0\0\0\x1f\x15\xc4\x89\0\0\0\rIDATx\x9cc\xf8\x0f\0\0\x01\x01\0\x05\x18\xd8N\0\0\0\0IEND\xaeB`\x82' > "$ROOT/outside/eval/views.png"
    
    if [ -n "${OC_DEV:-}" ]; then
      OC=(bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" "$OC_DEV/packages/cli/src/index.ts")
    else
      OC=(opencode)
    fi
    PORT=4599
    export OPENCODE_PASSWORD=repro
    curl() { command curl -u opencode:repro "$@"; }
    (cd "$ROOT/project" && "${OC[@]}" serve --hostname 127.0.0.1 --port $PORT --print-logs --log-level info > "$ROOT/server.log" 2>&1 &)
    for i in $(seq 1 60); do curl -sf "http://127.0.0.1:$PORT/api/project" >/dev/null 2>&1 && break; sleep 1; done
    API="http://127.0.0.1:$PORT"
    echo "== projects before"
    curl -s "$API/api/project"; echo
    echo "== open the session's project (as the app does)"
    curl -s -o /dev/null -w "%{http_code}\n" -G "$API/api/location" --data-urlencode "location[directory]=$ROOT/project"
    sleep 2
    echo "== projects after opening the project"
    curl -s "$API/api/project"; echo
    echo "== readLocalImage request: GET /api/fs/read/views.png?location[directory]=<image parent>/"
    curl -s -o /dev/null -w "%{http_code} %{content_type} %{size_download} bytes\n" -G "$API/api/fs/read/views.png" --data-urlencode "location[directory]=$ROOT/outside/eval/"
    sleep 3
    echo "== projects after showing the image"
    curl -s "$API/api/project"; echo
    echo "== fake MCP launches"
    cat "$ROOT/mcp-launches.log" 2>/dev/null
    echo "== server log lines mentioning the image folder"
    grep -n "outside/eval\|location services booted\|mcp" "$ROOT/server.log" | cut -c1-300 | tail -20
    pkill -f "serve --hostname 127.0.0.1 --port $PORT" || true

    Run it: bash repro.sh

  2. lsequeiraa commented on Oct 24, 2026

    @lsequeiraa
    Author

    Related: #58795. These stray image-folder locations never stay evicted while a web page is open, because the page's MCP refresh at eviction boots them again. So each one keeps its MCP servers and watchers loaded indefinitely, not just for an hour.

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