Repository navigation
Five @objectstack/cli e2e test files fail on macOS on a clean checkout (port-drift arms never see the drift they assert) #12884
Description
Activity
A sixth member of this same family, measured today on
e1c8d20eaand then confirmed on a clean detached worktree at the merge basedc75ba857:pnpm check:bash32-floorfails on macOS, in its own self-test, on an untouched tree.· real tree: 22 shell file(s) — 20 by extension, 2 by shebang alone — 0 finding(s) ✗ check-bash32-floor self-test failed (8 of 153 case(s)).Note the two halves disagree in the informative direction: the gate finds zero problems in the repo's actual shell, and it is the harness that fails. The first failing case names the cause:
✗ the simulated-3.2 harness really removes the builtin (else the next leg proves nothing) plain= sim.out= sim.err=…/probe.sh: line 1: mapfile: command not foundThe harness proves its simulated-3.2 leg is real by checking that
mapfileworks in a plain shell and not in the simulated one. On this host/bin/bashisGNU bash, version 3.2.57(1)-release (arm64-apple-darwin25)— Apple ships 3.2 — somapfileis absent from both legs,plain=comes back empty, and the harness correctly reports that it cannot tell its two legs apart. The gate is behaving honestly; it just cannot self-verify on the platform whose floor it exists to enforce.Exit code is 1, identical at the merge base with none of PR #12885's changes present (that PR's diff contains no shell file at all), so it is not attributable to any in-flight work.
Same practical consequence as the five e2e files above: a clean macOS checkout shows a red gate that CI (Linux, bash 5) shows green, which is the noise that trains readers to skip red. Worth either a host-capability skip for the simulation legs (run them only where the plain leg can be shown to have
mapfile) or a documented note.Generated by Claude Code
- addedpriority:p2Medium: important, M3Medium: important, M3and removed
on Aug 28, 2026 huangyiirene commented
on Aug 28, 2026 CollaboratorMore actions定级:
Bug·priority:p2·domain:cli·pm:queue分诊席位,session
session_01Aujz2zykf5LXt3T98gRsGe。domain:cli—— 卡体的五个成员全在packages/cli/test/**⇒ 车道表packages/cli归domain:cli。⚠️ 13:20Z 追加的第六个成员不在这个域,见下。priority:p2—— CI 跑 Linux,全部绿;影响面是开发者体验,⛔ 无产品影响、⛔ 不阻塞任何交付。Bug—— 测试把一个平台相关的行为当成有保证的前提去断言。⭐ 卡的判词准确:「That is a platform difference in how a held listening socket is (not) contended, not a defect in the code under test, and the tests are written as if the contention is guaranteed.」
⚠️ 第六个成员跨车道,建议拆卡13:20Z 追加的
check:bash32-floor落在scripts/⇒domain:devx,⛔ 不是本卡的域。建议单独立一张domain:devx卡,理由不只是归属:⭐ 它和前五个不是同一种缺陷。 前五个是"测试假设了一个平台不保证的行为"。第六个是:
这道门用「
mapfile在普通 shell 里可用、在模拟 3.2 腿里不可用」来自证两条腿真的不同。而 macOS 的/bin/bash就是 3.2.57(Apple 出厂),于是mapfile在两条腿里都不存在,plain=返回空,harness 诚实地报告"我分不出这两条腿"。⇒ ⭐ 一道强制 bash 3.2 下限的门,恰恰在唯一真正拥有 bash 3.2 的平台上无法自证。 它的自测因此只在下限不适用的平台上被证明过。
⚠️ 这是一个门的保证缺口,不只是 macOS 噪音 —— 值得它自己的卡和它自己的定级。⭐ 提交者的观察里还有一句要保留:「the two halves disagree in the informative direction」 —— 门对真实树的判定是
0 finding(s),失败的是 harness。⇒ 门本身在正常工作,⛔ 不要把它当成"门坏了"去修。
⚠️ 取卡者围栏- ⛔ 不得用 skip 换绿。 这是本仓红线(⛔ 永不跳过/禁用/隔离测试来获得绿色)。⭐ 首选是让 arm 变成宿主无关的:用一个能证明争用确实发生的方式去占端口,⛔ 而不是假设"占了就一定把子进程挤走"。
⚠️ 若确实做不到宿主无关,可接受的形态是能力探测型跳过(capability skip),且必须满足三条:① 跳过条件是实测到的能力缺失,⛔ 不是process.platform === 'darwin';② 跳过时打印为什么;③ 在 CI(Linux)上必须仍然执行 —— ⛔ 否则就是把覆盖率悄悄删掉。⭐ 第六个成员的提交者给的措辞正是这个形状:「run them only where the plain leg can be shown to havemapfile」。⚠️ 两个*-login-json-ndjson.e2e.test.ts尚未诊断(卡明说 "were not diagnosed further here",且它们是文件级失败,与三个serve-*的断言级失败形状不同)。⛔ 不要假设它们同因 ⇒ 取卡时先诊断,若成因不同则再拆一张。- ⭐ 卡对"为什么值得修"的论证要保留进 PR:「exactly the noise that trains people to ignore a red suite」。⛔ 这不是洁癖 —— 一个在干净检出上就红五个文件的仓库,会训练所有人对红色脱敏。
读数复现说明
⚠️ 本席位无法复现这些读数 —— 本会话运行在 Linux 容器上,⛔ 没有 macOS 宿主。⇒ 我采信提交者的两组读数(工作树 +dc75ba857干净分离检出,两次文件集与失败集完全一致)。⭐ 那组对照做得对:在合并基上复测排除了在飞改动,正是"这不是我的 diff 造成的"该有的证明方式。
Generated by Claude Code
不派发 —— 本席位的执行环境无法复现本卡的前提
domain:cli执行 PM 席位(#6024),sessionsession_01UjujZN219uFzBhSYfMykCd。保持pm:queue未认领,记录原因免得下一个席位重复推导后才发现。围栏没有问题:在
2026-08-29T00:26:24Z于origin/main=8cb96ec41推导(12 个未合并 PR / 72 行完整并集),这五个文件都空着。⇒ 挡住它的不是围栏,是环境。为什么不派
本卡的缺陷按定义是宿主平台差异:macOS(Darwin)上被占用的监听套接字不产生争用,于是
serve-*那三个测试断言的"漂移"根本不发生,断言反转。本席位的 dev 容器是 Linux —— 也就是卡里说的「CI runs Linux, so this is presumably green there and invisible」的那一侧。⇒ 派下去会得到一个结构上不可验证的交付:
- 复现:做不到 —— 缺陷在这里不出现;
- 修复后的验收("macOS 上五个文件转绿"):测不了;
- 消融(去掉修复必须变红):也测不了,因为红本身在这里不存在。
⚠️ dev 仍然能写出一个"看起来对"的改动(例如换一种占端口的写法),并且它在 Linux 上会全绿 —— 但那个绿什么都不证明,它在改动前就是绿的。这正是本席位这两天反复在防的那类空洞通过:一个测量在它本该失败的条件下根本没被执行(参见 #13014)。⛔ 所以这不是"暂时不做",是不该由这个席位做。
它需要什么才能派
一个 macOS 宿主,或一个能在 Linux 上忠实重放该套接字语义的替身 —— 而后者本身就是本卡要修的东西,不能拿它当验证手段(循环论证)。
给接手者的三条测量事实(已从卡中核对,非转述)
- 五个文件在
dc75ba857(ADR-0006 D2 (SDK half): rename client.projects.* to client.environments.*; unwrap keys follow the wire; JSDoc tells the truth #12866 的 merge base,不含该 PR 任何改动)上就已经全红,文件集与失败用例集逐一相同 ⇒ 与 ADR-0006 D2 (SDK half): rename client.projects.* to client.environments.*; unwrap keys follow the wire; JSDoc tells the truth #12866 无关,卡自己说得对:the point of this card is that it is caused by nothing in the diff。 - 三个
serve-*是同一个假设:测试用邻居套接字占住端口、期待子进程被挤开。断言原文:the child bound the port it was asked for — there is no drift to announce。 - 两个
*-login-json-ndjson.e2e.test.ts在两棵树上都是文件级失败,卡中未进一步诊断 ——⚠️ 不要假定它们与端口争用同因,那是三个serve-*的诊断,不是这两个的。
关联
- [finding]
getAvailablePortreturns a DIFFERENT port than requested and nothing says so — the drift is announced nowhere, and three test files have been silently talking past it #12543(已关闭)——getAvailablePort的漂移无人宣告,是同一族的生产侧;本卡是测试侧。 - flaky:
sdui_pick_free_portis a TOCTOU probe — it closes the probe socket before returning, so two concurrent callers scanning from 5180 are both handed 5180 (dequeued PR #10157 from the merge queue) #10167(已关闭)——sdui_pick_free_port的 TOCTOU 探测。⭐ 值得接手者一读:它的结论是"标准答案是不要关掉探测套接字",而本卡的三个serve-*恰恰依赖"占住不放会造成争用"这个宿主相关的假设 —— 两张卡指向同一个"如何可靠地持有一个端口"的问题。
Generated by Claude Code
⚠️ 本卡的读数已经过时 —— 生产侧与共享 harness 在它下面被改了(#13120,今天落地)domain:cli执行 PM 席位(#6024) · 会话session_01UjujZN219uFzBhSYfMykCd。⛔ 仍不认领(环境理由见 00:30Z 那条,未变:本容器是 Linux,缺陷按定义在这里不出现,交付会结构上不可验证)。这条只更新一个接手者必须知道、否则会拿旧读数下判的事实。发生了什么
PR #13120(卡 #13062,「
os servepublishes the port it BOUND, on all three announcement channels」)已合并为4f9df8f78。它改的正是本卡三个serve-*失败所围绕的那个量。改动面(
git show --stat):packages/cli/src/commands/serve.ts | 112 +++++- packages/cli/test/helpers/serve-process.ts | 32 +- packages/cli/test/serve-port-readback.e2e.test.ts | 26 +- packages/cli/src/commands/serve-bound-port-publication.test.ts | 244 +++ packages/cli/test/serve-publishes-bound-port.e2e.test.ts | 400 +++⭐ 为什么这动到了本卡的全部五个成员,而不只是一个
实测
origin/main,本卡点名的五个文件对共享 harness 的引用数:文件 helpers/serve-process引用serve-port-drift-notice.e2e.test.ts2 serve-port-readback.e2e.test.ts2 serve-process-child-env.e2e.test.ts4 login-json-ndjson.e2e.test.ts1 cloud-login-json-ndjson.e2e.test.ts1 ⇒ 五个全部经由
packages/cli/test/helpers/serve-process.ts,而该 helper 在 #13120 里被改了 32 行。⚠️ 包括那两个尚未诊断的*-login-json-ndjson文件 —— 它们是文件级失败,最可能的嫌疑本来就是共享启动路径,而共享启动路径刚刚变了。⭐ 而
serve-port-readback的载荷前提被重写了,不是顺手改了改#13120 把那条断言依据的量从「请求/漂移后的
port」换成「传输层报告它真正绑定的boundPort」,并在原地写明了理由:⚠️ It used to readresolveAuthBaseUrl(port), andportis only ALMOST the bound one:getAvailablePort()reassigns it past a dev auto-shift, which is the case this file drives, but it is still the number that was REQUESTED and it stays 0 under--port 0.⇒ 「this file drives」指的就是本卡里失败的那条路径。而本卡记录的 macOS 断言原文是:
AssertionError: the child bound the port it was asked for — there is no drift to announce : expected 54154 not to be 54154「asked for」与「bound」正是 #13120 分开的那两个量。 ⇒ 在 macOS 上这条失败是消失了、还是换了形状、还是原样,⛔ 无人测过 —— 本席在 Linux 上测不了,而 Linux 侧这五个文件本来就是绿的,所以这里的绿不构成任何证据。
给接手者的三条硬要求(在上一条的基础上追加)
- ⛔ 不许沿用本卡正文与 00:30Z 那条里的任何读数。 那些是
dc75ba857/e1c8d20ea上的测量,origin/main已经过了4f9df8f78。第一步必须是在当前 main 上于 macOS 宿主重测这五个文件,重新得到文件集与失败用例集。 ⚠️ 重测可能改变卡的形状,包括缩小到少于五个、或让「三个serve-*同因」这个分组不再成立。⛔ 不要为了保住原分组而把新读数往旧结论上凑。- ⭐ 上一条里那两条围栏不受影响,继续有效:⛔ 不得用 skip 换绿;若确实做不到宿主无关,可接受的形态是能力探测型跳过,且必须 ① 条件是实测到的能力缺失(⛔ 不是
process.platform === 'darwin')、② 跳过时打印原因、③ 在 CI(Linux)上仍然执行。
第六个成员(
check:bash32-floor,落在scripts/⇒domain:devx)不受本次改动影响,分诊建议的拆卡依然成立。
Generated by Claude Code
- ⛔ 不许沿用本卡正文与 00:30Z 那条里的任何读数。 那些是
出口核验(2026-09-09):本卡正确留在
pm:awaiting-maintainer—— 欠的是一次 macOS 读数,⛔ 不是一个决定维护者 2026-09-09 指示逐张核验 A 桶。核验结论:留桶,且欠的动作已被 R65 座位(
5503079593)压缩到最小。⛔ 决定半边已经做完,⛔ 不需要你再裁。已定的裁决(triage 围栏,⛔ 不重开)
把这几条断言改成主机无关地占住端口;若做不到,则用一个能力探测式跳过,且必须 ① 以实测能力为键、⛔ 不看
process.platform,② 打印原因,③ 在 CI 上照常执行。为什么不能直接派单
卡上原始读数取于 2026-08-28,此后 PR #13120(
4f9df8f78)重写了五个文件共用的packages/cli/test/helpers/serve-process.ts,并把「请求的端口」与「实际绑定的端口」拆开。⇒ 原读数已隔两次 harness 重写,照它派单会去追可能已不存在的数字。而每一个执行容器都是 Linux,本缺陷按定义是 macOS 主机差异 —— Linux 上绿在改动前就是绿的,⛔ 证明不了任何事。需要你做的(两条命令,贴回本卡)
在一台 macOS 主机、当前
origin/main上:pnpm --filter '@objectstack/cli...' build pnpm --filter @objectstack/cli exec vitest run \ test/serve-port-drift-notice.e2e.test.ts \ test/serve-port-readback.e2e.test.ts \ test/serve-process-child-env.e2e.test.ts \ test/login-json-ndjson.e2e.test.ts \ test/cloud-login-json-ndjson.e2e.test.ts贴回失败文件集 + 失败用例集(含断言原文)。有了这份读数,车道就能带着具体目标派发:让断言在前置条件不成立时响亮拒绝而不是反转;你再重跑同五个文件验收。
⚠️ 若这五个文件在当前 main 上已经在 macOS 上转绿,那也是一个完整读数 —— 贴「全绿」即可,本卡当场关闭。另一条同样合法的出口
R65 座位已明写:若你认为这只是 macOS 开发者侧噪声,一句话本卡按
not planned关闭。 判据是你自己的:本仓的 Mac 贡献者(含 agent 会话)在干净检出上会不会持续吃到五个红文件 —— 那正是 objectui#6596 裁过的「训练所有人忽略红色」的形状。⇒ 若 Mac 不在你的贡献路径上,关掉它是诚实的。⛔ 本席不替你在这两条出口之间选。
Generated by Claude Code
维护者回批:关闭(2026-09-09)
维护者对本卡的回批,逐字:「12884 关闭」。
⇒ 取本卡自
5503079593起就明写着的第二条出口 ——「若你认为这只是 macOS 开发者侧噪声,一句话本卡按not planned关闭」。⛔ 不是因为缺读数而搁置,是决定不做。按
not planned关闭,同笔摘掉pm:awaiting-maintainer(SKILL.md:133:关闭即在同一笔摘掉pm:*状态标)。domain:cli、priority:p2留下 —— 归属不是状态。随本次关闭一并被接受的代价,写下来而不是默认掉
- 五个文件在 macOS 干净检出上继续红:
serve-port-drift-notice/serve-port-readback/serve-process-child-env/login-json-ndjson/cloud-login-json-ndjson。三个serve-*的成因已定位——测试用邻居 socket 占住端口、指望被 spawn 的os serve子进程被挤开以便观察漂移,而该主机上子进程照样绑上了那个端口,于是断言反转:expected 54154 not to be 54154。⛔ 这不是被测代码的缺陷,是「争用必然发生」这个前提在该平台不成立。 - 两个
*-login-json-ndjson从未被诊断(原卡即注明「failed at file level on both trees and were not diagnosed further here」)⇒ 关闭意味着它们保持未诊断状态。 - CI 不受影响:CI 跑 Linux,这五个文件在那里是绿的。本次关闭 ⛔ 不削弱任何门禁、⛔ 不跳过任何测试、⛔ 不动 ratchet —— 没有任何东西被标记为跳过,只是这份 macOS 差异不再被追。
⚠️ 若日后 Mac 进入常规贡献路径(人或 agent),这五个红文件会重新变成 objectui#6596 裁过的那个形状(健康态下的红训练所有人忽略红)。届时重开本卡即可,读数与成因都在上面,⛔ 不需要重新诊断。
Generated by Claude Code
- 五个文件在 macOS 干净检出上继续红:
Observation, filed unassigned while running
pnpm --filter @objectstack/cli testfor #12866. Not caused by that change — the point of this card is that it is caused by nothing in the diff.What was measured
On a macOS host (Darwin 25.5.0),
pnpm --filter @objectstack/cli testreports:The same five files were then run on a CLEAN worktree detached at
dc75ba857(the merge base, with none of #12866's changes present) after building--filter '@objectstack/cli...':Identical file set, identical failing-test set. The five:
test/serve-port-drift-notice.e2e.test.tstest/serve-port-readback.e2e.test.tstest/serve-process-child-env.e2e.test.tstest/login-json-ndjson.e2e.test.tstest/cloud-login-json-ndjson.e2e.test.tsThe shape of the port ones
All three
serve-*failures are the same assumption: the test holds a port with a neighbour socket and expects the spawnedos servechild to be pushed OFF it, so the drift can be observed. On this host the child binds the held port anyway, so there is no drift and the arm inverts:serve-port-readback's load-bearing arm then readspromise resolved "{ stdout: '', …(1) }" instead of rejecting— same cause one layer up.That is a platform difference in how a held listening socket is (not) contended, not a defect in the code under test, and the tests are written as if the contention is guaranteed.
Why it is worth a card
CI runs Linux, so this is presumably green there and invisible — but every agent and every contributor working this repo on a Mac gets five red files on a clean checkout, which is exactly the noise that trains people to ignore a red suite. Either the port arms need a host-independent way to hold a port (or a documented skip), or the platform gap should be recorded so the next reader does not re-diagnose it.
The two
*-login-json-ndjson.e2e.test.tsfiles failed at file level on both trees and were not diagnosed further here.Generated by Claude Code