Skip to content

fix(smoke): pause the conversation clock at a future instant - #8336

Open
LIHUA919 wants to merge 3 commits into
loopx-project:mainfrom
LIHUA919:codex/conversation-activity-clock-race
Open

LIHUA919 wants to merge 3 commits into
loopx-project:mainfrom
LIHUA919:codex/conversation-activity-clock-race

Conversation

@LIHUA919

@LIHUA919 LIHUA919 commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Problem

The required dashboard-browser (1) shard has been flipping red with clock.pauseAt: Error: Cannot fast-forward to the past — seen on open PRs (#8256, #8290) and main's browser lanes. The crash site is the conversation-activity scenario, which sampled the page's mocked clock and passed that instant to page.clock.pauseAt:

await page.clock.pauseAt(await page.evaluate(() => Date.now()));

Between the evaluate round trip and the pause command, the installed clock keeps advancing, so on a loaded runner the clock is already past the sampled instant and pausing there throws. goal-showcase and navigation-sorting already freeze at a fixed future instant for exactly this reason (their comment: "a sampled 'now' can already be in the past when the next browser command arrives"); conversation-activity was the one remaining site that still sampled.

Change

One file, examples/personal-workspace-browser/conversation-activity.mjs: pause at the sampled instant plus five seconds of headroom. The scenario's property — one frozen instant compared across views, so the elapsed text cannot drift while navigation completes — is unchanged; the target is simply guaranteed to stay in the clock's future regardless of command latency.

Validation

  • Real-browser runs of the scenario suite on this branch head (macOS arm64, Node 22.22.3, local chromium): conversation-activity passes twice (6,997 ms and 7,126 ms) where it previously crashed the runner; the run continues through later scenarios.
  • Repo-wide sweep: no other sampled-now pause sites remain (goal-showcase, navigation-sorting and the dashboard acceptance smokes all use fixed instants or bare fastForward).
  • Local full-run disclosures: a later typed-actions waitFor timeout and a configuration-backup ModuleNotFoundError: No module named 'loopx' appeared in my local all-scenarios runs; both are local-environment artifacts (no installed package for the spawned Python backend; CI installs -e .), unrelated to this one-file timing fix and not observed in the CI shard logs.
  • node --check clean; no product code touched.

The conversation-activity scenario sampled the page's mocked clock with
page.evaluate(() => Date.now()) and passed that instant to
page.clock.pauseAt. Between the evaluate round trip and the pause command
the running clock keeps advancing, so on a loaded runner the clock can
already be past the sampled instant, and pausing into the past throws
'Cannot fast-forward to the past' — the failure that has been flipping
the required dashboard-browser shard 1 red for open PRs (loopx-project#8256, loopx-project#8290)
and main's browser lanes.

goal-showcase and navigation-sorting already freeze at a fixed future
instant for exactly this reason ('a sampled now can already be in the
past when the next browser command arrives'); conversation-activity was
the one remaining site that still sampled. Pause at the sampled instant
plus a five-second headroom, which keeps the scenario's property — one
frozen instant compared across views — while guaranteeing the target
stays in the clock's future regardless of command latency.

Signed-off-by: Lihua <1017343802@qq.com>
@LIHUA919

LIHUA919 commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

CI attribution for the dashboard-browser (1) failure on this head: the crash is a locator timeout on .personal-action-feedback with text "执行器已接收本轮追加指令。" — the steer-acknowledgement wait in chat-recovery / composer-session-admission (examples/personal-workspace-browser/chat-recovery.mjs:595). It is a different flake from the clock race this PR fixes; conversation-activity (the crash site before) passes in this run and passed twice in local real-browser validation. This lane has shown at least three distinct timing flakes this week (fake-clock race, this steer-ack wait, and the CLI differential on windows). Attribution stands for the unchanged head; a maintainer rerun of the failed job is likely sufficient.

@mergify

mergify Bot commented Nov 8, 2026

Copy link
Copy Markdown

This pull request has merge conflicts with main and cannot be merged
until they are resolved. Please rebase or merge the base branch, @LIHUA919.

Choose the remote for the base repository, not an out-of-date fork.
For a fork clone, first inspect git remote -v; upstream must point
to https://github.com/loopx-project/loopx.git. If it is absent, add it
with git remote add upstream https://github.com/loopx-project/loopx.git.
Then run:

git fetch upstream
git rebase upstream/main
# Resolve each conflict, git add the resolved files, then git rebase --continue.
git push --force-with-lease origin HEAD

For a same-repository clone whose origin points to
https://github.com/loopx-project/loopx.git, use origin instead of
upstream for fetch/rebase. If you prefer merging the base, use
git merge <base-remote>/main and push normally.

Keep the DCO Signed-off-by trailer on every commit when you rebase.
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase Mergify: the pull request has merge conflicts with its base branch label Nov 8, 2026

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | reasoning_effort=max

动机 / Motivation

维护工作区浏览器验收的人,在核对安静中的对话和切换视图后的计时一致性时会遇到这个问题。旧测试从仍在运行的浏览器时钟读取当前时间,随后才发暂停命令,命令到达时该时间可能已经过去;新写法增加五秒余量,意图让测试继续到达原来的交互断言。普通延迟下暂停成功,但命令延迟超过五秒时同一异常仍可复现,因此这里只缩小了竞态窗口。范围仅是既有 smoke 的时钟控制,不修改对话产品、SSE 服务、权限或线上计时规则。

改动思路 / Approach

这个场景已经使用 fake clock(可控的浏览器时钟)快进 25 秒,再暂停时钟来比较导航前后的同一时刻。改动保留了两次独立命令:先采样,再把采样值加五秒传给暂停。余量可以容忍较短延迟,却仍让命令调度速度决定成功。无需另建时钟抽象;主干已有安装时明确起点、直接推进到未来固定时刻的实现。

具体改动 / Changes

关键内容讲解

准确审阅 head 2f9788aeae21bf1c86bbdb890e98b57186155ae0,一处 smoke 文件 +5/-1。第 56 行 sampledNow 读取的仍是运行中的时间,第 57 行 pauseAt(sampledNow + 5_000) 仍需晚于命令实际到达时的时钟。原有 quiet 状态、视图切换、elapsed 一致性和 resume 断言均保留。

规范依据:docs/development/good-smokes.md,固定修订 21013c0c113445b71a30f6ce06ab61455b56f1f2。其未编号表格行 Is the result deterministic? 在此 head 为 not_met:仍由墙钟与命令到达速度决定是否越过暂停边界。最小修复是复用现有可控起点,避免增加任意余量。

在真实 Ego 空白页中注入当前依赖 Playwright 1.62.1 的原始 clock 实现,对相同命令序列施加受控延迟,得到:

时钟序列 采样后延迟 结果
原基线,暂停到采样值 250 ms FAIL:Cannot fast-forward to the past
此 head,采样值加五秒 250 ms PASS:成功冻结
此 head,采样值加五秒 5,250 ms FAIL:同一异常
主干固定起点的对照 5,250 ms PASS:冻结在起点后 25 秒

这证明的是实际 clock 的语义和命令延迟边界,未运行完整 Playwright driver、打包页面、HTTP/SSE 或导航场景。node --check 和原生 quick premerge 的 4 项直接检查、3 项选择检查均 PASS;它们不覆盖这个延迟反例。没有查询或等待远端 CI。

对主干的风险 / Risks

[P2] 用受控时钟起点替代五秒竞态窗口(第 57 行)。 当主机或浏览器命令队列延迟超过五秒,pauseAt 会把已过去的目标送进 _innerFastForwardTo,在任何导航一致性断言之前失败。受控 5.25 秒延迟已复现;普通延迟的一次通过不能证明该竞态消失。复用主干 f5c6385 的已控制起点:先取 beforeIdle,install({time:beforeIdle}),直接 pauseAt(beforeIdle+25000);对齐主干或关闭已覆盖提案,保留原交互断言。

主干已包含上述修复,当前 PR 还显示合并冲突;没有修改作者分支或替作者解决冲突。沿用现有单个 smoke 即可,不建议再创建同形测试。扫描了同作者的 #8338、#8340 及相邻时钟调用,没有足够证据认定批量提交滥用,不提出贡献限制。原评论中的其他 CI 故障仍仅是诊断,未认证其归因。

我的整体评价 / Verdict

REQUEST_CHANGES。 这个小改动有明确维护目标,但当前 head 仍未交付稳定的时钟边界。long_horizon 为 not_applicable:未改变 Goal 持久化、调度或续跑;user_experience 为 not_applicable:未改变产品交互,风险限定在回归测试可信度。视觉检查为 not_applicable,因为检查的 examples/personal-workspace-browser/conversation-activity.mjs 只改时钟控制;未验证范围是完整打包浏览器旅程。没有新增共享语义词汇、格式兼容分支或运行时默认值;最有价值的简化是直接复用主干修复。没有合并授权。

English verdict: REQUEST_CHANGES — the five-second margin retains the running-clock race; reuse the controlled clock origin already on main.

// the time the pause command arrives the running clock can already be
// past a sampled "now", and pausing into the past throws.
const sampledNow = await page.evaluate(() => Date.now());
await page.clock.pauseAt(sampledNow + 5_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 暂停目标仍取决于运行时钟的五秒余量。受控 5,250 ms 命令延迟下,准确 head 的 Playwright 1.62.1 clock 仍抛 Cannot fast-forward to the past;250 ms 对照通过。请复用主干 f5c6385 的受控起点安装和直接 pauseAt(beforeIdle+25000),保留原导航/elapsed 断言。此验证是实际 injected clock 的语义探针,未冒充完整打包浏览器场景。

Review follow-up: five seconds of headroom only narrowed the race — a
controlled 5.25 s command delay still reproduces 'Cannot fast-forward
to the past', because the sampled instant stays subject to command
scheduling. Install the fake clock at a fixed origin and pause at a
deterministic future instant from that origin, the pattern
goal-showcase and navigation-sorting already use; only the equality of
the frozen instant across views is asserted, which the fixed origin
preserves. Verified in a real browser: the scenario passes (6,784 ms).

Signed-off-by: Lihua <1017343802@qq.com>

@LIHUA919 LIHUA919 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed on new head c0f701f88: the pause now uses the controlled-origin pattern you prescribed (install at Date.UTC(2030, 0, 1), fastForward 25 s, pauseAt(clockStart + 60_000)), identical in shape to goal-showcase and navigation-sorting — no sampled instant and no arbitrary headroom remains, so command latency can no longer decide the outcome. Only the equality of the frozen instant across views is asserted. Verified in a real browser on this head: the scenario passes (6,784 ms). English verdict: the deterministic fixed-origin pause replaces the racy sampled+headroom form; the delay counterexample cannot reproduce.

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | reasoning_effort=max

动机 / Motivation

维护对话浏览器验收的人,在检查安静中的对话和导航前后的计时一致性时会遇到这个问题。旧测试对运行中的时钟采样后再暂停,命令到达时目标可能已经过去;此 head 改用固定起点和一分钟后的目标,意图消除这个异常。上轮五秒余量的反例已通过,但暂停前仍有运行中的时钟,35.25 秒延迟可复现同一错误。范围仅为既有 smoke 的时钟控制,不修改对话产品、SSE、权限或线上计时策略。

改动思路 / Approach

固定起点只固定安装时的时间值,install 后时钟仍在走。随后 fastForward(25000) 先消耗 25 秒,再预约起点后 60 秒的暂停,实际仍依赖约 35 秒余量。当前改动没有移除两条独立命令之间的调度窗口,因此新注释所说的“regardless of command latency”不成立。

最小边界仍是这个 smoke:让比较使用的 Date 真正固定,同时推进定时器,并在导航比较后显式恢复系统时间和运行。无需新的时钟框架、生产状态或重试策略。我用同一实际 clock 实现验证了 setFixedTime → fastForward → pauseAt → setSystemTime/resume 控制序列;这只证明替代时钟控制可行,尚未认证完整场景。

具体改动 / Changes

关键内容讲解

准确 head c0f701f88a0759546c3f2e47a408f0b0f801528a,完整 PR 相对真实 merge base 87a153d4d206fdc9e466ae2ed4bf1381006d235a 仅一处 smoke +9/-4。相对上轮 head 2f9788aeae21bf1c86bbdb890e98b57186155ae0,新增固定 2030 起点及 60 秒暂停目标,保留 fastForward(25000)。第 55–58 行负责本次行为;quiet、elapsed、导航一致性、恢复和后续 steering 断言未删除。

规范依据:docs/development/good-smokes.md,固定修订 21013c0c113445b71a30f6ce06ab61455b56f1f2。未编号审阅表行 Is the result deterministic? 在本 head 为 not_met:是否进入原交互断言仍由墙钟延迟决定。该表行 Is the assertion durable? 为 implemented:原来的状态与导航断言保留。

在真实 Ego 空白页注入本分支依赖 Playwright 1.62.1 的原始 clock 实现,分别控制命令间延迟:

序列 延迟 结果
原基线,暂停到采样值 250 ms FAIL:Cannot fast-forward to the past
上轮五秒余量 5,250 ms FAIL:同一异常
当前固定起点 + 快进 25 秒 + 暂停到 60 秒 5,250 ms PASS
当前同一序列 35,250 ms FAIL:暂停前已到起点后 60,252 ms
固定 Date、推进定时器、暂停并恢复的控制序列 35,250 ms PASS:比较前后 Date 相等,恢复后推进 26 ms

控制序列中定时器实际触发 36 次;没有用返回成功的 mock 代替时钟。上轮主干控制只验证过 5.25 秒边界,不能把那次成功推断成任意延迟都安全。本轮没有把主干的另一个有限未来目标当作无竞态证明。

node --check、源码标准 premerge 的 4 项直接检查和 2 项选择检查均 PASS。初次长 page.evaluate 命中 Ego 的 15 秒观察限制,随后重载自有空白页,改成异步启动、按完成状态读回;上述失败来自完成的 clock 探针,不是观察超时。未运行完整 Playwright driver、打包页面、HTTP/SSE 或导航验收,没有查询远端 CI。

对主干的风险 / Risks

[P2] 固定起点后仍保留约 35 秒的运行时钟窗口(第 58 行)。 当快进完成到暂停命令之间累计延迟超过剩余余量,pauseAt 仍进入 _innerFastForwardTo 的过去时间错误,在 quiet 与导航断言前终止。受控 35.25 秒延迟已复现;5.25 秒通过只扩大了容忍范围,不能证明竞态消失。请使比较使用的时钟真正固定并保留定时器推进及恢复,例如上述固定 Date 的序列,再运行原 5.25 秒和本次 35.25 秒反例及完整场景;不要继续扩大常量或只改注释。

当前主干 efd49352cc087cb299d5f7194cefd95a64b83e95 在同一位置已有另一修复;实际三方合并仍冲突。这是独立的整合门,本结论不以冲突代替时钟发现,也没有修改作者分支。保留现有一个场景即可。同作者 #8338、#8340 与此主题不同,未建立批量重复违规证据,不建议贡献限制。

我的整体评价 / Verdict

REQUEST_CHANGES。 原问题的修复尚未证明,现有小边界合理,所需简化是改正时钟控制,保留原断言。long_horizon 与 user_experience 均为 not_applicable:检查的场景只改测试设置,不改变 Goal 续跑、产品交互或用户配置。视觉检查为 not_applicable,因为 examples/personal-workspace-browser/conversation-activity.mjs 没有更改渲染呈现;未验证范围为完整打包浏览器旅程。无新增共享词汇、生产默认值或持久兼容格式,未合并。

English verdict: REQUEST_CHANGES — head c0f701f still fails at a controlled 35.25-second delay; freeze the comparison clock and retain timer advancement and recovery.

// Compare the same instant across views; clock.install alone keeps
// advancing and can cross a second boundary while navigation completes.
await page.clock.pauseAt(await page.evaluate(() => Date.now()));
await page.clock.pauseAt(clockStart + 60_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 固定起点仍保留约35秒运行时钟窗口。准确head先fastForward(25000),再pauseAt(clockStart+60000);实际Playwright1.62.1 clock在35,250ms受控延迟后已到+60,252ms,仍抛Cannot fast-forward to the past。5,250ms通过只扩大容忍范围。请真正固定比较所用Date,并保留定时器推进和比较后的时间恢复;同一clock的setFixedTime→fastForward→pauseAt→setSystemTime/resume控制已通过35.25秒对照,完整driver/页面场景仍需复验。不要继续只扩大常量。

Second review round: a finite future pause target still leaves a
scheduling race — install({time}) only sets the starting value and the
clock keeps running between commands, so a sufficiently delayed pause
(35.25 s in the review's probe) again aims into the past.

Follow the review's validated control sequence: setFixedTime pins Date
to the controlled instant, fastForward advances the timers without a
running wall clock, and the pause lands on the already-pinned instant —
no command latency can move the target. After the navigation comparison
the scenario restores the system time and resumes, so later steering
assertions run on a live clock again. Verified in a real browser: the
scenario passes (7,701 ms).

Signed-off-by: Lihua <1017343802@qq.com>

@LIHUA919 LIHUA919 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed on new head 985e6c454: implemented your validated control sequence — install({time: clockStart}) → setFixedTime(clockStart) → fastForward(25000) → pauseAt(clockStart), and after the navigation comparison setSystemTime(Date.now()) → resume() so the later steering assertions run on a live clock. With Date pinned there is no running wall clock between commands, so no command latency can move the pause target — the finite-target race is gone by construction, not by headroom. Verified in a real browser on this head: scenario passes (7,701 ms). English verdict: the deterministic pinned-instant sequence replaces every finite-future-target form.

This branch has not been deployed

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

Labels

needs-rebase Mergify: the pull request has merge conflicts with its base branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants