Skip to content

fix(studio-server): time history idle limits on one clock so a slow machine cannot flip them - #4924

Merged
miguel-heygen merged 3 commits into
mainfrom
fix/history-test-injected-clock
Oct 3, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
fix/history-test-injected-clock

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

What

History decides when an agent's turn has gone idle on one clock, and the tests that check that limit advance that clock instead of sleeping. A slow machine no longer changes which writes a turn keeps, in the tests or in a preview whose window timer fires late.

Why

Three tests in packages/cli/src/commands/history.test.ts used pause(ms) against historyDeps.turnIdleMs with wall-clock margins (600 ms gaps under a 1000 ms limit, 600 or 700 ms over a 300 or 400 ms limit). When a command takes long enough to eat the margin, the gaps between writes cross the limit and a turn that should stay the agent's is filed as Outside. The test for writes closer together than the idle limit is the one that flips.

History had three clocks behind that decision:

  • the engine took an injectable now but still called Date.now() for sweep times and window touches (projectHistory.ts, three places);
  • the CLI stamped a direct turn's start with Date.now() (historyOwner.ts);
  • each write is dated by its file's change time, clamped to the sweep time.

Change

  • The engine uses its own now() everywhere it reads the time.
  • A window's idle timer no longer ends the window by itself: when it fires, the window ends only if the engine's clock says it has been idle for its limit, and otherwise re-arms for the rest. With the real clock this is the same timing as before; with an injected clock the timer can no longer end a window the clock says is still live. Each sleep is capped at Node's timer range (about 24.8 days), so a longer idle limit sleeps and checks again rather than waking after 1 ms, which is what Node does with a longer delay.
  • historyDeps.now is the CLI's clock: it stamps turn starts, is passed into the engine it opens, and dates the "ago" column.
  • The tests set that clock a day ahead of the file system and stamp the mtime of each file they write through the test helper with it, so those writes are dated when the test says. pause() is gone; the three tests advance the clock instead. Writes the tests do not make through the helper (the baseline files, the turn marker, undo's own writes, renames and deletes) keep real file times; no test here times one of those against the idle limit.

The default clock is Date.now().

Proof

Each history command slowed by 1.5 s (a delay added to the test's command runner for this run only):

Before After
Slowed 1 failed: "a turn whose writes come closer together than the idle limit stays the agent's past it" 32 passed
Normal 32 passed 32 passed

Each command finishing 0.8 s late, preview mode, "a turn left open ends after the idle limit": with the engine's old bare timer the window ended before the agent's write was seen and the test failed; with the clock check it passes. Disabling the idle rule itself (projectHistory.ts, the takesWrite check) now fails that test in both direct and preview mode.

The engine's own history suites (packages/studio-server/src/history) pass: 138 passed, 2 skipped. The new one opens a window with a 30-day idle limit and fails if any timer delay exceeds Node's range.

Under 100 changed lines: a standalone fix of the history idle clock with nothing in flight to carry it.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Edit accuracy: accurate 1216 (base branch 1216), smooth 1058 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (1)

@miguel-heygen miguel-heygen changed the title test(cli): time history turns on one injected clock instead of real waits fix(studio-server): time history idle limits on one clock so a slow machine cannot flip them Oct 3, 2026
@miguel-heygen
miguel-heygen marked this pull request as ready for review October 3, 2026 01:47

@terencecho terencecho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved at 14831b3ceea324b1892ad4cce9d75bfc68eb2a56.

The engine and CLI tests share an injected clock and advance it rather than relying on wall-clock sleeps. The production engine and direct CLI paths still default to Date.now; the preview server is not given the test clock. The idle timer's remaining-time recheck and the tests' fake file mtimes are consistent with that clock. I found no introduced behavior regression. A separate watcher-debounce/timer ordering edge exists identically at base and head; this review does not claim that edge is fixed.

The exact-head Studio and runtime tests, Windows engine/CLI tests, and all 11 required checks passed. I did not rerun the PR body's 1.5-second slowed-command probe locally.

— Review by tai (pr-review)

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit 9123628 Oct 3, 2026
169 of 170 checks passed
@miguel-heygen
miguel-heygen deleted the fix/history-test-injected-clock branch October 3, 2026 02:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants