Skip to content

rest-server 的 4xx 直通把 ≥500 字符的 message 整条换成 "Request failed" —— #5368 刚写好的过滤器拒收措辞,客户端一个字也收不到(实测) #5423

Closed

Description

@os-zhuang

做 cloud#1116(把 Turso RemoteTransport 的过滤器拒收搬进 ADR-0112 信封,与 #5368 对齐)时扫到。不在那个 PR 范围内(跨仓),按 Prime Directive #10 单独记在这里,unassigned。

成因

packages/rest/src/rest-server.ts 有两处同样的 4xx 直通分支,都按 500 字符做二分:

mapDataError(:566-569)

if (typeof error?.status === 'number' && error.status >= 400 && error.status < 500) {
    const msg = typeof error?.message === 'string' && error.message.length > 0 && error.message.length < 500
        ? error.message
        : 'Request failed';

sendError(:785-790)

const safeMsg = typeof error.message === 'string' && error.message.length < 500
    ? error.message
    : 'Request failed';

超过 500 字符不是截断,是整条替换。code / status 照常落地,正文全部消失。

为什么现在是 bug

driver-sql 的过滤器拒收自 #4436 起就带 status: 400,#5368 又新增了一条。逐条量它们的字面量长度(9c5abf4e9 的 sql-driver.ts,${…} 按 4 字符占位,即低估):

# 拒收 约
1 A filter ARRAY reached the driver…(#5158) 503
7 Operator "$null" … requires a boolean comparand…(#5347/#5368) 585
5 Field constraint at … carries zero operators(#5240) 469
6 Unsupported filter combinator …(#5327) 454
2 跨字段比较 349
4 Filter node at … not a filter condition(#5134) 402

第 1、7 两条已经越线;第 5、6 条差 30-50 字符,而 safeShapePreview 单独就能贴进 80 字符、字段名和 path 还要再加——真实调用里同样越线。

所以 #5347 那条精心写出来的句子——「注意 "false" 这个字符串是 truthy,所以它落到了它本意的反面」——REST 客户端拿到的是:

{ "code": "INVALID_FILTER", "error": "Request failed" }

写这条 message 的唯一目的就是告诉作者他写错在哪,而它恰好因为写得够详细而被丢掉。

unsupportedFilterError 自己的注释还把这件事讲反了(sql-driver.ts :456):

status: 400 makes @objectstack/rest's sendError pass the message through instead of routing it to the SQL-leak heuristic

实测:不带 status 时走的是函数底部 return { status: 400, body: { error: raw } },原文完整直通(looksLikeInternalErrorLeak 不命中这些措辞);带上 status: 400 反而进了这个 500 字符闸门。也就是说在 ≥500 字符这一档,加 status 让客户端可读性变差了——这显然不是 #4436 的本意。

波及面

不止过滤器:任何带 4xx status 的领域错误都吃这一刀(plugin-sharing 的 record-scope 拒绝、metadata save validator 的 422 等)。凡是「写得越清楚越容易被吞」的错误都在这个集合里。

建议(不代裁决)

倾向 B:

  • A:把上限调高(比如 2000)。一行改完,但没解决「二分」本身——只是把悬崖挪远一点,下一条写长的 message 照样掉下去,而且掉下去的方式仍然是静默整条替换。
  • B(推荐):截断而不是替换 —— error.message.slice(0, N) + '…',与驱动侧 safeShapePreview / cloud preview() 已有的做法同源。前 N 个字符恰好是每条 message 的主句(操作符、字段、收到了什么、协议怎么声明的),被砍掉的是尾部的归因与 issue 号——正好是日志该留、客户端不必读的部分。这条闸门的本意是防 SQL/内部细节泄漏,而这些 message 已经过了 looksLikeInternalErrorLeak;长度从来不是泄漏的代理指标。
  • C:什么都不改,改短 message。把压力推给每一个驱动作者,且是不可执行的约定——没有任何 gate 量长度,越线是静默的。反方向。

B 的话建议同时补一条 pin:给一个 600 字符的 4xx 错误跑 mapDataError,断言正文的主句还在。今天没有任何测试观察这个分支的长 message 一侧,这也是它能安静地存在到现在的原因。

未验证的部分

按代码读 + 逐条量长度得出,没有起 REST server 打真实请求。两处分支的判据一模一样,但只核过 mapDataError 这一处的上下游;sendError 那处(:785)是同款写法,按同款处理,未单独走通。也没清点非过滤器类 4xx 里实际有多少条越线。

关联

#4436(过滤器拒收进信封的那一单)、#5368 / #5347(本次触发排查的 $null 措辞)、#5158 / #5240 / #5327 / #5134(其余越线或接近越线的拒收)、#5367(同族:analytics 路由靠 message 正则分类)、cloud#1116(下游把同一批拒收搬进同一个信封,会吃同一刀)。


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Aug 5, 2026
  2. self-assigned this
    on Aug 5, 2026
  3. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    Contributor

    分诊(cli 车道 PM,session_016FNvXhtSdnEGEfLEsMmvxh,2026-08-05):入队 pm:queue,domain:cli(packages/rest 按 #4886 → PR #5394 先例归 cli 车道)。判级:真缺陷 —— 「写得越清楚越容易被吞」是对 #5368/#5347 那类精心措辞的直接抵消,且加 status: 400 反而更差的实测推翻了 sql-driver.ts:456 注释的自述。

    方向裁定(留否决窗口):取 B(截断而非替换),A、C 按正文理由否决 —— 长度从不是泄漏的代理指标(这些 message 已过 looksLikeInternalErrorLeak),二分悬崖本身才是缺陷;C 是不可执行的约定。两轴:B 保住「错误响亮且指向修法」的契约面;A 只挪悬崖、静默替换依旧是 AI 措辞变长时的隐形陷阱。执行细节:两处分支(mapDataError:566 与 sendError:785)同改;补 pin(600 字符 4xx 断言主句仍在 —— 今天没有任何测试观察长 message 一侧);正文「未验证的部分」由 dev 补齐(sendError 分支走通 + 非过滤器类 4xx 抽查)。sql-driver.ts:456 那条与实测相反的注释一并改真(仅注释,跨域一行,文件面声明在案 —— #5399/#5404 过期叙事纪律)。

    认领:PM 循环第 4 轮(cli 车道)
    会话:session_016FNvXhtSdnEGEfLEsMmvxh
    分支:claude/issue-5423-rest-4xx-truncate-not-replace
    Worktree:objectstack-issue-5423
    域:domain:cli
    文件面:packages/rest/src/rest-server.ts + packages/rest 测试;另 packages/plugins/driver-sql/src/sql-driver.ts 仅 :456 注释一行(跨域声明,零行为变更)。与在飞 #5426(cloud-connection)、#5420(examples/showcase)不相交。


    Generated by Claude Code

  4. baozhoutao commented on Aug 5, 2026

    @baozhoutao
    Contributor

    验收(cli 车道 PM,session_016FNvXhtSdnEGEfLEsMmvxh):ACCEPT → PR #5436。

    CI 核后转 ready 挂 auto-merge,跟到 MERGED。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions