Skip to content

desktop: opening a built-in skill fails with "Location not found: /builtin" / 桌面版:点击内置技能弹出「Location not found: /builtin」 #60439

Closed

Description

@trt44343

Description

EN — In Settings → Skills, clicking any of the three built-in skills (Browser, OpenCode, Report) shows a toast: "Failed to load file / Location not found: /builtin". Their content is loaded fine — only viewing it fails. The fourth skill, reverse-engineer-anything, opens normally.

中文 — 在「设置 → 技能」里点击三个内置技能(Browser / OpenCode / Report)中的任意一个,都会弹出「加载文件失败 / Location not found: /builtin」。它们的内容其实正常加载了,只是没法查看。第四个技能 reverse-engineer-anything 可以正常打开。

Mechanism / 机制

Built-in skills register a placeholder path rather than a real file. GET /api/skill returns:

id path content length
opencode /builtin/opencode.md 12736
report /builtin/report.md 4622
browser /builtin/browser.md 11234
reverse-engineer-anything C:\Users\<user>\.agents\skills\reverse-engineer-anything\SKILL.md 23125

内置技能注册的是占位路径,不是真实文件:

Opening a skill resolves its path through the file API, splitting it into a read target and a location[directory]:

  • GET /api/fs/read/builtin/browser.md → 404
  • GET /api/fs/read/C:\Users\<user>\.agents\skills\...\SKILL.md (control) → 200, 23523 bytes
  • GET /api/location?location[directory]=/builtin → 404
{"_tag":"LocationNotFoundError","location":{"directory":"/builtin"},
 "message":"Location not found: /builtin"}

点击技能时,界面会把 path 拆成「文件名 + location[directory]」再走文件接口,于是 /builtin 被当成目录传给服务端。这个目录在磁盘上不存在,返回体与弹窗文案完全一致。

The content itself is fine — it is compiled into the CLI and returned in full by /api/skill. Only the "open as file" path is broken.

技能内容本身没问题——它编译在 CLI 里,/api/skill 会完整返回。坏的只是「当文件打开」这条路径。

Expected / 预期

Clicking a built-in skill shows its content read-only, the same way a file-backed skill opens.

点击内置技能后,以只读方式显示其内容,和基于真实文件的技能一致。

Actual / 实际

A toast appears: "Failed to load file / Location not found: /builtin". Nothing opens.

弹出「加载文件失败 / Location not found: /builtin」,什么都不打开。

Impact / 影响

  • 3 of the 4 default skills cannot be inspected, on every install and every platform.
  • The wording ("failed to load file") reads like data loss or a corrupt install, so it generates support noise for a purely cosmetic defect.
  • 默认 4 个技能里有 3 个无法查看,任何安装、任何平台都如此。
  • 文案是「加载文件失败」,看起来像数据丢失或安装损坏,实际只是显示层缺陷。

Suggested fix / 修复建议

Any one of these resolves it:

  1. Serve built-in skill content from the registry (e.g. GET /api/skill/{id}/content) and have the view open that instead of a filesystem path.
  2. Add an explicit builtin: true flag to the skill record so the UI opens a read-only virtual document from the content field it already has.
  3. Give the placeholder a scheme that cannot be mistaken for a resolvable path (e.g. opencode://builtin/browser.md) instead of a POSIX-looking absolute path.

任选其一即可:① 用专门的接口返回内置技能内容;② 给技能记录加 builtin: true 标记,界面直接读 content 字段;③ 把占位路径换成不会被误当成真实路径的形式(如 opencode://builtin/browser.md)。

Related / 相关

#57247 (closed as duplicate of #50951) — same LocationNotFoundError text, but a different cause: a remembered project folder that was deleted, not a built-in skill path.

#57247 也是同一个报错文案,但根因是「记住的项目文件夹被删除」,与内置技能路径无关。

Plugins

None. One MCP server is configured (rea, [email protected]), which is how the fourth skill is loaded.

OpenCode version

2.2.2 (desktop app, bundled CLI v2.2.2)

Steps to reproduce

  1. Open Settings → Skills. / 打开「设置 → 技能」。

  2. Click Browser, OpenCode, or Report. / 点击 Browser、OpenCode 或 Report 任意一个。

  3. A toast appears: "Failed to load file / Location not found: /builtin". / 弹出「加载文件失败 / Location not found: /builtin」。

  4. Control: click reverse-engineer-anything — it opens normally. / 对照:点击 reverse-engineer-anything 可正常打开。

Screenshot

No response

Operating System

Windows 11 10.0.22631.5472, x64

Terminal

Not applicable (desktop app)

Activity

  1. opencodejr commented on Oct 30, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report. I reproduced this on 2.2.2, and it still happens on the latest dev (653650d).

    GET /api/skill lists the built-in skills with the placeholder paths /builtin/opencode.md, /builtin/report.md and /builtin/browser.md, and returns their full content. Looking up those paths through the location or file API, which is what happens when a skill row is clicked, returns 404 LocationNotFoundError: Location not found: /builtin. That is the toast you saw. A skill backed by a real file on disk resolves normally. The script below shows this against the server API. I didn't test it by clicking through the desktop UI.

    One small note: in 2.2.2 the rows under Settings → Extensions → Skills don't respond to clicks. The clickable skills list is the one in the session details panel, so that is probably where you clicked.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces anomalyco/opencode#60439 at the server API level.
    # Built-in skills report placeholder paths (/builtin/*.md); resolving them
    # through the location/file API (what the app does when a skill row is
    # clicked) fails with "Location not found: /builtin".
    # Usage: ./repro.sh            -> uses `opencode` from PATH
    #        OC_DEV=/path/to/repo ./repro.sh -> runs the CLI from a checkout
    set -u
    PORT=${PORT:-4599}
    PROJ=$(mktemp -d /tmp/oc60439.XXXX); git -C "$PROJ" init -q
    LOG="$PROJ/serve.log"
    cd "$PROJ"
    if [ -n "${OC_DEV:-}" ]; then
      bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" \
        "$OC_DEV/packages/cli/src/index.ts" serve --hostname 127.0.0.1 --port "$PORT" >"$LOG" 2>&1 &
    else
      opencode serve --hostname 127.0.0.1 --port "$PORT" >"$LOG" 2>&1 &
    fi
    PID=$!
    trap 'kill $PID 2>/dev/null' EXIT
    for _ in $(seq 60); do grep -q 'server password' "$LOG" && break; sleep 1; done
    PASS=$(sed -n 's/^server password //p' "$LOG")
    B="http://127.0.0.1:$PORT"; D="location%5Bdirectory%5D"
    req() { curl -s -w " [HTTP %{http_code}]\n" -u "opencode:$PASS" "$@"; }
    
    echo "== GET /api/skill (id, path, content length)"
    # first call for a new location may return [] while skills load; warm up
    curl -s -o /dev/null -u "opencode:$PASS" "$B/api/skill?$D=$PROJ"; sleep 3
    curl -s -u "opencode:$PASS" "$B/api/skill?$D=$PROJ" | python3 -c \
      'import json,sys;[print(" ",s["id"],s.get("path"),len(s.get("content",""))) for s in json.load(sys.stdin)["data"]]' 2>&1
    echo "== GET /api/location?location[directory]=/builtin"
    req "$B/api/location?$D=/builtin"
    echo "== GET /api/fs/read/browser.md?location[directory]=/builtin"
    req "$B/api/fs/read/browser.md?$D=/builtin"
    echo "== control: a file-backed skill path resolves"
    mkdir -p "$PROJ/.agents/skills/demo"; printf -- '---\nname: demo\ndescription: demo skill\n---\nhello\n' >"$PROJ/.agents/skills/demo/SKILL.md"
    req "$B/api/fs/read/SKILL.md?$D=$PROJ/.agents/skills/demo"

    Run it: bash repro.sh

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