排错指南
常见错误码、报错现象与修复步骤。
插件开发中遇到的大部分问题可以归为四类:清单校验失败、权限被拒、视图/协议错误、会话与运行时失效。本节按"现象 → 原因 → 修复"组织,先看错误码速查表,再按分类定位。
错误码速查
| 错误码 / 异常 | 属于 | 一句话含义 |
|---|---|---|
permission_denied | 宿主响应 | 未声明所需权限 |
unknown_command | 宿主响应 | 当前插件类型/IDE 不支持该 SDK 命令 |
invalid_context | 宿主响应 | 信封不属于当前插件会话(会话已过期) |
manifest_* | 清单校验 | plugin.toml 存在结构或语义错误 |
delivery_paused | 宿主响应 | 插件连续失败,视图补丁投递被暂停 |
view_not_mounted / component_not_found / method_not_supported / invalid_arguments / operation_failed | 宿主响应 | 组件命令式调用失败 |
ViewProtocolError | 本地异常 | 快照/补丁超过上限或视图已关闭 |
StaleReferenceError | 本地异常 | 运行时重启后使用了已经失效的 runtime 引用 |
RuntimeUnavailableError | 本地异常 | 当前后端不支持运行时检查 |
SymbolResult.stale | 业务结果 | 符号请求期间文档修订发生变化 |
TransportClosedError | 本地异常 | 传输层已关闭(插件已卸载/会话结束) |
PluginContextError | 本地异常 | 宿主注入的上下文缺失或非法 |
区分两类错误:宿主拒绝的请求通过回调 error= 收到(SdkApiError 子类);本地就能判断的问题在调用处直接抛出异常。详见 错误体系。
插件无法启动或激活失败
manifest 校验报错
插件安装 / 打包时会在插件详情页显示错误码。常见取值与修复:
| 错误码 | 含义 | 修复 |
|---|---|---|
manifest_missing | 找不到 plugin.toml | 确认文件在插件目录根 |
manifest_invalid_toml | TOML 语法错误 | 检查括号、引号 |
manifest_unsupported_version | manifest_version 不是 2 | 改为 manifest_version = 2 |
manifest_invalid_plugin_id | 插件 ID 不合法 | 仅用小写字母、数字、-、_ |
manifest_invalid_contribution_id | 贡献 ID 未使用 插件ID. 前缀或重复 | 统一改为 id = "<插件ID>.<名称>" 且全局唯一 |
manifest_invalid_activation_event | 激活事件目标不是本插件贡献的 ID | 检查 onView: / onCommand: 目标 |
manifest_invalid_permission | 权限名不存在 | 对照 Manifest 权限表 修正 |
manifest_invalid_when | when 表达式语法错误 | 用文档列出的上下文键重写 |
激活事件没命中
插件已安装但从不启动,通常是因为触发的事件与 activation_events 不匹配:
- 视图插件需要在
activation_events里声明onView:<viewId>;只在清单里声明视图贡献(contributes.views)不会让插件自动启动。 - 命令插件需要
onCommand:<commandId>。 - 想让插件在 IDE 启动时就运行,声明
onStartup(Ui/Service)或使用 DataPlugin。
启动钩子抛异常
on_start() 抛出的异常会让宿主把插件标记为 failed 并停止会话。检查 IDE 输出面板中的 traceback;常见原因包括在 on_start 里访问了未声明的权限、视图 ID 写错、或试图同步等待回调结果。
权限被拒(permission_denied)
调用 API 时收到 PermissionDeniedError:
def on_result(error=None, **cb):
if isinstance(error, PermissionDeniedError):
print("缺少权限:", error.required_permission)
self.file.read_file("/boot.py", callback=on_result)修复步骤:
- 用
error.required_permission确认缺哪个权限(例如file.read)。 - 在
plugin.toml的permissions列表里补上,重新打包并重装插件——清单是在安装时解析的,只改文件不重装不会生效。 - 权限默认拒绝:不要假设"既然是 UI 插件就自动有文件权限"。
视图不显示或渲染异常
| 现象 | 原因 | 修复 |
|---|---|---|
| 视图空白 | view_id 与贡献 ID 不一致 | 两边统一为 id(如 device-console.status) |
| 视图空白 | 未声明 ui.view 权限 | 补权限并重装 |
| 视图空白 | 视图模型创建后没 open() | 调用 view.open() |
| 一直转圈(环形指示器不停) | 手动传的 instance_id 与宿主挂载点不一致,快照发到了没人渲染的实例 | 侧边栏视图直接用 self.views.create(view_id) 不传 instance_id(SDK 会按 manifest 默认成 container:<容器 id> 对齐宿主);标签页视图用 tabs.create_view 回调里的 instance.instance_id |
| 数据没出现 | 数据在 on_start 返回后才异步送达 | 用回调接力,别在 on_start 里顺序等待 |
| 补丁被暂停 | delivery_paused | 插件近期有大量失败帧;检查代码,等待 IDE 恢复投递后自动 resync |
ViewProtocolError(本地抛出)
快照/补丁超过硬性上限时,会在发送前本地抛出:
- 单次快照节点数 >
MAX_SNAPSHOT_NODES(20000) - 单次补丁操作数 >
MAX_PATCH_OPS(2000) - 快照/补丁负载 >
MAX_VIEW_PAYLOAD_BYTES(2MB)
修复:分页发送(如 VirtualList 的 item_count + on_request_range,或 TreeView 的 on_request_children),不要一次性推送全部数据。
回调不执行或结果为空
- 没有处理回调中的
error:失败响应会以error关键字传入,cb里没有业务字段。请显式检查cb.get("error"),不要用if cb判断成功。 - 没传
callback:即发即弃方法(如self.editor.set_text)立即返回、没有回调;需要结果的 API 才传callback。 - 回调里做了阻塞操作:回调运行在 Bridge 事件循环线程,
time.sleep或同步 IO 会卡住整个插件。 - 事件订阅被拒:主题未知或权限不足时订阅在本地被丢弃,通过
callback(error=...)感知。
会话与运行时失效
InvalidPluginContextError(invalid_context)
会话已过期:通常是 IDE 端重启了会话或运行时,而插件仍有属于前一会话的请求在途。重新获取当前上下文后再发起请求。
StaleReferenceError / RuntimeUnavailableError
运行时后端重启后,已有的 runtime.* 引用全部失效:
StaleReferenceError— 使用了重启前的对象引用。RuntimeUnavailableError— 当前后端不支持运行时检查(能力不可用)。
修复:订阅 runtime.backend.restarted(self.runtime.on_backend_restarted),在回调里重新枚举会话 / 重建视图。
SymbolResult.stale
documents.symbols() 返回的 SymbolResult.stale 为 True,表示文档在符号请求期间发生了变化。这是正常业务结果,不会抛出本地异常。重新获取当前文档及其修订号后再请求符号。
TransportClosedError
传输层已关闭:插件被卸载、停用或运行时重启。属于预期清理路径,把该异常当作"会话结束"处理,不要阻塞退出。
插件卡死或无法再次启停
单例解释器共享同一个运行时。如果一个插件没有正常退出(例如后台线程不结束、on_dispose 抛异常),会阻塞该插件(甚至影响其他插件)的启停。
处理办法:
- 在 IDE 运行时管理里"重启运行时"整体恢复。
- 根因排查:确认后台任务在
on_dispose里被取消、DataPlugin 用run_once()而非start()。
单例解释器的全局状态污染
所有插件共享同一个 Python 解释器:
- 模块级 / 全局变量在插件间可见,务必用
插件ID.前缀命名空间。 - 插件内用
__main__模块名或全局单例的代码可能与其他插件冲突。 - 需要隔离时,尽量把状态放在
Plugin实例属性里,而不是模块全局。
快速定位流程
报错了吗?
├─ 是清单校验错(manifest_*)→ 见「manifest 校验报错」
├─ 是回调里的 error → 看 error.code:
│ ├─ permission_denied → 补权限
│ ├─ unknown_command → 检查插件类型/命令 ID
│ ├─ invalid_context → 会话过期,重试
│ └─ 其他 SdkApiError → 按 message 定位
├─ 是本地异常:
│ ├─ ViewProtocolError → 检查视图负载上限
│ ├─ StaleReference/RuntimeUnavailable → 重启后重建引用
│ └─ TransportClosed → 会话已结束,正常
└─ 没有报错但功能不对 → 检查激活事件 / 权限 / 视图 ID / 回调时序