前言

最近OpenClaw很火,个人感觉体验其实一般,没什么质的飞跃,核心还是自己配skill和各种工具,真要算就是加上了远程控制。

我认为 Happy + Claude Code 就足够了,但之前能启动的Happy最近死活启动不成功。

于是有了本文,本文内容是AI的尝试和AI总结,本人润色优化,仅作记录。

happy.png

Happy是一个 monorepo 项目,包含:

  • happy-cli:终端侧
  • happy-app:移动端
  • happy-server:服务端

我最开始是通过 npm install -g happy-coder安装的 CLI。一开始启动完全正常,但使用一段时间后,本地启动开始 反复报错,而且有时候报错还不一致。

想着借助AI来定位修复看看,经过反复的调试,修复了多个看起来"像 bug"的问题,但都无济于事,最后发现是 Happy 的使用方式搞错了。

第一层问题:CLI 启动直接崩溃

最开始的报错非常简单:

Cannot read properties of undefined (reading 'id')

有时候甚至只打印:

Error

定位过程

顺着堆栈追到:

packages/happy-cli/src/api/api.ts

问题代码类似这样:

1
2
response.data.session.id
response.data.machine.id

如果服务端返回:

HTTP 200

但响应结构缺失:

1
2
3
{
  "session": null
}

代码就会直接触发:

Cannot read properties of undefined (reading 'id')

这属于典型的 缺少防御性校验

修复方案

AI在几个关键位置增加了校验。

1. getOrCreateSession

1
2
3
4
5
6
const raw = response.data?.session

if (!raw || typeof raw.id !== 'string') {
  logger.warn('Invalid session response', response.data)
  return null
}

避免直接访问:

undefined.id

2. getOrCreateMachine

同样增加校验:

1
2
3
4
5
6
const raw = response.data?.machine

if (!raw || typeof raw.id !== 'string') {
  logger.warn('Invalid machine response', response.data)
  return createMinimalMachine()
}

3. API Client 构造函数

防止非法对象继续流入系统:

1
2
3
if (!session?.id) {
  throw new Error("Invalid session object")
}

修复效果

这样即使服务端返回异常数据结构:

  • CLI 不会直接崩溃
  • 能输出日志用于排查

看起来问题解决了。

但实际上 只是刚开始。

第二层问题:机器注册失败并误判离线

修完第一轮错误后,CLI 可以启动了。

但日志开始出现新的问题:

Machine registration failed: invalid_response
Server returned success but response missing machine.id

接着 CLI 会把服务器标记为:

offline

实际情况

服务端其实返回了:

HTTP 200

只是响应 body 不符合 CLI 的结构预期。

也就是说:

服务端成功了,但 CLI 误判失败。

修复思路

原代码在这里调用:

1
connectionState.fail()

这会导致客户端认为服务器不可达。

AI改成:

只记录日志,不标记失败。

1
2
3
4
if (!raw || !raw.id) {
  logger.warn('Invalid machine response')
  return createMinimalMachine()
}

这样:

  • 不影响后续 session 创建
  • CLI 仍继续运行

第三层问题:Remote Mode React Hook 报错

解决上面的问题后,CLI 可以进入 Remote Mode

但新的错误又出现了:

Invalid hook call
Cannot read properties of null (reading 'useState')

这类报错通常只会出现在一种情况:

React 实例不一致

原因分析

Happy CLI UI 是基于:

  • ink
  • react

而 CLI bundle 内部又打包了一份 React。

结果运行时出现:

ink -> React A
CLI bundle -> React B

React hook dispatcher 只挂在:

React A

但组件调用的是:

React B.useState()

于是就变成:

null.useState

修复方案

强制 CLI 使用 ink 的 React 实例

在 launcher 中:

1
2
3
const ReactForInk = createRequire(
  require.resolve('ink')
)('react')

然后渲染 UI:

1
ReactForInk.createElement(RemoteModeDisplay)

在组件内部:

1
2
3
function RemoteModeDisplay({ react }) {
  const { useState, useEffect } = react
}

所有 hooks 都从传入的 React 获取。

这样整棵组件树只使用 一个 React 实例

Hook 报错消失。

第四层问题:CLI 与 App 消息通信异常

再往后,看似是正常了,但实际还是不可用:

happy-cli 能收到 happy-app 的消息

但:

CLI 发出的消息是否到达 app 不确定

于是AI在发送路径增加了日志,例如:

Sending envelope
Flushed N message(s)
flushOutbox failed

从日志看:

CLI -> server
POST 请求基本成功

偶尔失败也会自动重试。

但:

app 端仍然收不到消息

后续经过反复的尝试,我发现偶尔又能通过已经构建好的Cli成功启动。

最终发现:问题其实不在代码

修了这么多代码后,我开始意识到一个问题:

为什么 Happy 一开始是能正常运行的?

于是重新检查使用方式。

最后发现问题其实非常简单:

我用错了 Happy 的运行模式。

Happy 的正确使用方式

Happy CLI 不是设计成在多个目录分别运行的

正确流程是:

1 初始化绑定

1
happy

2 程序后台启动守护进程

1
happy daemon # 无需手动执行

3 APP 通过守护进程创建 session

start

而不是:

1
2
3
4
5
cd projectA
happy

cd projectB
happy

这样会产生:

多个 CLI 实例
多个 session
多个 machine 注册

导致状态混乱。

如果需要重新初始化环境,可以删除:~/.happy/,重新绑定。

总结

瞎折腾半天也没有成功,但好在结果是好的,也不枉费一番尝试。

Claude Code 的出现开始,越来越直观地感受到 AI 的迭代速度,如何更好的将 AI 融入日常工作中,提升效率,需要持续的探索。