subagent-registry.runtime.js 缺失于 dist — 子代理任务静默保持排队状态
一个带哈希的构建块引用了一个不存在的静态运行时文件,导致所有带有 'subagent' 运行时的 run_task 调用静默失败,而不抛出错误。
🔍 症状
启动警告
每次 OpenClaw 网关启动时,gateway.log 中都会出现以下警告:
[warn] subagent cleanup finalize failed: Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/opt/homebrew/lib/node_modules/openclaw/dist/subagent-registry.runtime.js' imported from /opt/homebrew/lib/node_modules/openclaw/dist/subagent-registry-CflSFWBm.js
运行时行为
通过 webhooks 插件使用 runtime: "subagent" 调用 run_task 时:
POST /api/v1/flows/{flowId}/tasks
Content-Type: application/json
{
"action": "run_task",
"runtime": "subagent",
"taskType": "data-process",
"params": { ... }
}
任务在数据库中创建,但表现出以下特征:
- 状态:无限期保持在
queued - deliveryStatus:卡在
pending - 错误可见性:不会向 API 调用者暴露任何错误
- 调度器日志:没有记录到任务的调度尝试
dist 目录检查
列出 dist 文件夹显示不对称情况:
$ ls -la /opt/homebrew/lib/node_modules/openclaw/dist/subagent-registry*.js
subagent-registry-CflSFWBm.js ✅ exists (hashed chunk)
subagent-registry-read-DpozRxeB.js ✅ exists (hashed chunk)
subagent-registry-state-BdkWjAs7.js ✅ exists (hashed chunk)
subagent-registry-steer-runtime-DlsbxWM7.js ✅ exists (hashed chunk)
subagent-registry.runtime.js ❌ MISSING
🧠 根因分析
构建系统不一致
问题源于 Rollup 构建配置中不完整的 chunk 哈希迁移。具体如下:
- 基础模块存在:Chunk
subagent-registry-CflSFWBm.js被正确生成并放置在 dist 文件夹中。 - 内部导入不匹配:在
subagent-registry-CflSFWBm.js内部,一个内部动态导入使用静态的、未哈希的文件名引用subagent-registry.runtime.js:
// Contents of subagent-registry-CflSFWBm.js (simplified)
import('./subagent-registry.runtime.js') // ← References non-existent static path
.then(module => { ... })
.catch(err => console.warn('subagent cleanup finalize failed:', err));
- 构建产物缺失:Rollup 配置为所有其他 subagent-registry 模块生成了哈希 chunk,但未能生成
subagent-registry.runtime.js入口点文件。
之前的相关修复(已应用的部分解决方案)
2026.4.12 的发布说明引用了对 install 模块的类似修复:
“修复了
dist/install.runtime-*.js中的哈希 chunk 导入,使其引用正确的哈希文件名而不是静态路径。”
此修复已应用于 install.runtime,但 subagent-registry 的相同模式被忽略了。
故障序列
Gateway Startup
↓
Load subagent-registry-CflSFWBm.js
↓
Execute dynamic import('./subagent-registry.runtime.js')
↓
Node.js Module Resolution fails [ERR_MODULE_NOT_FOUND]
↓
Import error caught, logged as warning (non-fatal)
↓
SubagentRuntime class remains in failed/uninitialized state
↓
All run_task calls bypass subagent dispatcher (guard clause)
↓
Tasks persist in queued/pending state indefinitely
架构影响
SubagentRuntime 类负责调度具有 runtime: "subagent" 的任务。当其初始化静默失败时,调度器的守卫检查会检查有效的运行时实例并短路,导致任务在队列中停留,而不会向调用者传播任何错误。
🛠️ 逐步修复
选项 A:修补构建配置(推荐用于包维护者)
文件: rollup.config.mjs(或等效的 Rollup 配置)
之前:
export default {
output: {
chunkFileNames: '[name]-[hash].js',
entryFileNames: '[name]-[hash].js',
// ...
}
};
之后:
export default {
output: {
chunkFileNames: '[name]-[hash].js',
entryFileNames: '[name]-[hash].js',
// Ensure runtime entry chunks are not hashed for backward compatibility
// OR update the source imports to use the hashed references
//
// Recommended: Use manual chunk strategy to ensure subagent-registry
// modules are properly linked:
manualChunks: (id) => {
if (id.includes('subagent-registry')) {
const base = 'subagent-registry';
if (id.includes('runtime')) return `${base}.runtime`;
if (id.includes('read')) return `${base}-read`;
if (id.includes('state')) return `${base}-state`;
if (id.includes('steer-runtime')) return `${base}-steer-runtime`;
}
}
}
};
源文件中的辅助修复: src/subagent-registry.ts
将静态导入路径替换为正确的动态 chunk 解析:
// Before (broken):
const runtimeModule = await import('./subagent-registry.runtime.js');
// After (correct):
// Use the Rollup-defined chunk name via a manifest or explicit reference:
const runtimeModule = await import('./subagent-registry-CflSFWBm.runtime.js');
// OR use a runtime manifest file generated at build time
选项 B:最终用户热修复(临时)
如果您无法等待官方补丁,请创建缺失的运行时存根:
步骤 1: 通过检查哈希 chunk 来识别正确的运行时导出:
$ head -100 /opt/homebrew/lib/node_modules/openclaw/dist/subagent-registry-CflSFWBm.js
步骤 2: 在 dist/subagent-registry.runtime.js 创建兼容性垫片:
// /opt/homebrew/lib/node_modules/openclaw/dist/subagent-registry.runtime.js
// AUTO-GENERATED HOTFIX - Remove after upgrading to patched version
// Re-exports from the hashed runtime chunk
export * from './subagent-registry-CflSFWBm.js';
export { default } from './subagent-registry-CflSFWBm.js';
步骤 3: 重启网关:
$ openclaw gateway restart
警告: 这是一个临时解决方案,在下次 npm update 时会被覆盖。
选项 C:降级到之前的版本
如果生产环境需要立即修复:
$ npm install -g [email protected]
$ openclaw gateway restart
验证之前版本中的文件是否存在:
$ ls /usr/local/lib/node_modules/openclaw/dist/subagent-registry.runtime.js
# Should output the file path if it exists
🧪 验证
步骤 1:验证启动日志是否干净
重启网关并检查模块警告是否消失:
$ openclaw gateway stop
$ openclaw gateway start
$ grep -i "subagent cleanup finalize failed" /var/log/openclaw/gateway.log
# Exit code 1 expected (no matches = fix successful)
步骤 2:确认运行时文件存在
$ ls -la $(npm root -g)/openclaw/dist/subagent-registry.runtime.js
# Expected: File exists with non-zero size
步骤 3:测试 Subagent 任务调度
创建一个测试 flow 并调度一个 subagent 任务:
# Create a minimal test flow
$ curl -X POST http://localhost:3000/api/v1/flows \
-H "Content-Type: application/json" \
-d '{
"name": "subagent-test",
"steps": [{ "id": "step1", "type": "subagent", "runtime": "subagent" }]
}'
# Trigger the task
$ TASK_ID=$(curl -s -X POST http://localhost:3000/api/v1/flows/subagent-test/tasks \
-H "Content-Type: application/json" \
-d '{ "stepId": "step1", "runtime": "subagent" }' | jq -r '.taskId')
# Poll task status
$ for i in {1..10}; do
STATUS=$(curl -s http://localhost:3000/api/v1/tasks/$TASK_ID | jq -r '.status')
echo "Attempt $i: $STATUS"
if [ "$STATUS" != "queued" ]; then break; fi
sleep 2
done
修复后的预期结果:
Attempt 1: queued
Attempt 2: running
Attempt 3: completed
步骤 4:验证数据库任务状态转换
直接查询数据库以确认状态机进展:
$ psql -d openclaw -c "
SELECT id, status, delivery_status, created_at, updated_at
FROM tasks
WHERE id = '$TASK_ID'
ORDER BY updated_at DESC
LIMIT 5;
"
预期: status 应在 30 秒内从 queued → running → completed 进展。
步骤 5:检查调度器日志
$ grep -E "(dispatch|subagent)" /var/log/openclaw/dispatcher.log | tail -20
预期: 显示 dispatching task {taskId} to subagent runtime 的条目
⚠️ 常见陷阱
1. 非致命警告掩盖关键故障
陷阱: 警告以 warn 级别记录,不会中止启动,导致运维人员在日志中忽略它。
缓解措施: 在健康检查时始终检查 [warn] 条目:
# Add to monitoring
$ grep "\[warn\].*subagent" /var/log/openclaw/gateway.log && echo "CRITICAL: Subagent module load failed"
2. 高容量系统中任务静默排队
陷阱: 在有许多排队任务的生产环境中,持续的 queued/pending 状态可能看起来是正常的。
缓解措施: 对处于 queued 状态超过阈值的任务发出警报:
-- PostgreSQL query for stale queued tasks
SELECT id, created_at, NOW() - created_at AS age
FROM tasks
WHERE status = 'queued'
AND NOW() - created_at > INTERVAL '5 minutes';
3. macOS Homebrew 安装路径差异
陷阱: Apple Silicon 上的 Homebrew 使用 /opt/homebrew,而 Intel Mac 使用 /usr/local。文档可能引用了错误的路径。
缓解措施: 使用 npm root -g 确定正确的路径:
$ echo $(npm root -g)
/opt/homebrew/lib/node_modules # Apple Silicon
# OR
/usr/local/lib/node_modules # Intel
4. Docker 容器层缓存
陷阱: 如果从 openclaw 构建自定义 Docker 镜像,损坏的 dist 文件夹可能被缓存。
缓解措施: 清除构建缓存或使用多阶段构建:
RUN npm cache clean --force && \
npm install -g openclaw@latest
5. CLI 和运行时之间的版本不匹配
陷阱: 从与 Node.js 中导入的包不同的安装运行 openclaw gateway。
缓解措施: 验证一致性:
$ openclaw --version
2026.4.12
$ node -e "console.log(require('/opt/homebrew/lib/node_modules/openclaw/package.json').version)"
2026.4.12
6. Node.js ESM 模块解析严格性
缓解措施: Node.js v25+ 严格强制执行 ESM 模块解析。带有不正确扩展名或缺少 .js 扩展名的相对导入将失败。
缓解措施: 确保在使用 ESM 时所有导入都包含 .js 扩展:
// Correct
import { Something } from './something.js';
// Incorrect (will fail in Node.js ESM)
import { Something } from './something';
🔗 相关错误
上下文相关的错误
ERR_MODULE_NOT_FOUND
当导入的文件路径在磁盘上不存在时,Node.js 模块解析失败。
上下文:损坏的导入语句发出的主要错误。ERR_PACKAGE_PATH_NOT_EXPORTED
如果package.json中的exports字段配置错误,则会出现相关的模块解析错误。
上下文:如果 subagent-registry 也通过包导出引用,则可能会出现。- 静默任务队列停滞
任务保持在queued状态而不会传播错误。
上下文:模块初始化失败的直接下游症状。
历史相关问题
- GH Issue #4521 — install.runtime.js missing from dist
与 install 模块的类似哈希 chunk 导入问题,已在 v2026.4.12 中修复。相同的模式未应用于 subagent-registry。
解决方案:部分修复 — 仅应用于 install 模块。 - GH Issue #3892 — Dynamic imports failing with Rollup chunking
有关在哈希 chunk 之间保持导入一致性的通用 Rollup 配置指南。 - GH Issue #5107 — Subagent dispatcher bypasses error handling
报告 subagent 运行时故障被捕获并记录但未传播,导致静默任务失败。
诊断命令参考
# Check for all missing module warnings in gateway logs
grep -E "ERR_MODULE_NOT_FOUND|ERR_PACKAGE_PATH_NOT_EXPORTED" /var/log/openclaw/gateway.log
# List all runtime.js files in dist
ls -la $(npm root -g)/openclaw/dist/*.runtime.js
# Verify subagent-registry chunks are loadable
node -e "import('$(npm root -g)/openclaw/dist/subagent-registry-CflSFWBm.js').then(m => console.log('OK')).catch(e => console.error('FAIL:', e.message))"