我的AI审计工具是合并了Claude和Cursor会议. 错误是一个 UNIQUE 限制 .

2026年9月7日21 次浏览来源:Dev.to阅读原文

我维护着Chron,一个MCP服务器,将AI编码会话的审计日志写到本地的SQLite数据库.

每个消息都会得到时间戳,每个会话都记录产生它的工具:克洛德(Claude, Cursor, Codex).

不久前,我打开历史 发现克劳德的消息 和克塞尔的消息 在一个会话中相互连接。

两个不同的工具,两个不同的终端,一个会话记录.

对于一个整个工作都是归属的工具来说,这和虫子得到的一样糟糕.

设置 会话表看起来是这样的:当一个工具开始工作时,它用标题和它自己的名称召唤。

如果已经存在该标题的会议,请恢复。

否则就另起炉灶 Claude和Cursor都从任务中产生一个简短的描述性标题.

在同一repo中执行相同的任务,它们产生相同的标题.

类似的东西,然后保证 只有一行可以存在 该标题。

因此第二个工具没有获得自己的会话.

它恢复了第一个。

Claude的讯息和Cursor的讯息在同一会话ID下登陆,在该会话上,无论哪个工具碰巧先开始,都会报告.

限制是完全按照书面规定去做的。

这只是错误的制约。

标题从来不是会话的特征。

这对是的。

修补一个尝试,这行不通 明显的举动: 这是错误的,而且它是错误的,因为它通过一个偶然的测试。

无效。

在SQL中,从不等于.,这包括一个独特的指数内的比较.

约束停止了对列为NULL的一瞬间的限制:在一个独特的索引下两行相同.

这是标准的 SQL 行为,而不是 SQLite 的夸克,但很容易忘记您在复合键上添加一栏可失效的时刻.

这里之所以重要,是因为Chron有合法的NULL行:会话通过库 API 创建,没有工具集.

固定索引是一个表达式,而不是生列: NULL倒闭为空字符串,它确实比较了自己: 每个工具一行,在一个工具内复制,仍然被拒绝.

这就是我真正想要的财产。

当然,搜索代码必须匹配。

继续只用标题搜索,所以即使有正确的限制,它也会一直找到另一个工具的行:一个限制和依赖它的查询是一个单元.

换掉的只有一只 丢弃 SQLite SQLite 中的制约没有 。

所上线是表定义的一部分,因此去掉它意味着重建表:标准阴影表舞.

没什么奇怪的事 与原始错误相比的迁移错误在这里是值得的。

我首先写到移民警卫: 如果综合指数不存在,迁移。

它的读取非常灵敏, 并且它失败了 正是数据库需要它。

计程靴在移民检查前运行。

在仍带有遗留内容的现有数据库中,该声明是成功的。

它在旧的桌子上创造了综合指数.

旧的束缚仍然坐在那里,没有改变。

因此,当迁移检查运行时,索引存在,警卫得出结论"已经迁移",它跳过.

桌子永远保持。

新鲜的数据库是好的,因为它们从一开始就是正确的。

测试套房通过,因为测试建立了新的数据库.

每一个真正的用户升级 从旧版本,会一直保留错误。

警卫必须询问制约,而不是通常与之相关的副作用.

唯一能看出限制的方法就是所存储的 DDL: Regex 对抗 DDL 不优雅.

然而,这是事实。

测试必须从旧计划开始 而不是从新计划开始 我从这个UNIQUE的限制中得到了一个身份证明。

声称"标题表示一个会话".

这绝不是事实,数据库忠实地执行了虚假的说法,直到两个工具出现之日。

综合独有索引中可忽略的列通常不会做你想做的.

如果列可以是 NULL,则索引一个快取

分享