Skip to content

Case study: apache/echarts#21538

apache/echarts#21538 (open when this was recorded) reports that roundTime_locale in test/ut/spec/util/time.test.ts fails in any time zone with daylight saving time. A maintainer in a zone without DST runs the same test and it passes. That is the “works on my machine” situation twin is built for.

Every file referenced here is in examples/echarts-21538. All runs were recorded on Solari on 2026-09-28.

File What it is
new-york.json capsule from a reporter in America/New_York: the test fails
kolkata.json capsule from a maintainer in Asia/Kolkata: the test passes
replay.txt twin replay new-york.json
bisect.txt twin bisect new-york.json --good kolkata.json
fix.patch a candidate fix for the test helper
verify.txt twin verify new-york.json --patch fix.patch
mcp.txt a scripted agent session against twin mcp: inspect, replay, run, write_file, run, verify, release
Run on Solari Result Time
twin replay new-york.json REPRODUCED, identical failure signature 72 s
twin bisect new-york.json --good kolkata.json minimal difference TZ=America/New_York 69 s
twin verify new-york.json --patch fix.patch FIXED in the reporter’s environment, nothing pushed 71 s
MCP session (twin mcp, 7 tool calls) reproduced, fixed on the kept machine, FIXED on a fresh one, released 162 s

Both capsules are from echarts commit 984bf46 (master on 2026-09-12), Node 22.23.3, npm 10.9.9, captured with a minimal environment (env -i keeping only PATH, HOME, LANG and TZ). On one machine, the same command ran under two time zones:

npx jest --config test/ut/jest.config.cjs --coverage=false test/ut/spec/util/time.test.ts

TZ=America/New_York fails with exactly the numbers from the issue; TZ=Asia/Kolkata passes. The New York capsule’s summary:

Command
argv npx jest --config test/ut/jest.config.cjs --coverage=false test/ut/spec/util/time.test.ts
cwd .
result FAIL (exit 1) in 0.8s
signature exit1:0aee0834ac9c4d2d
key lines FAIL test/ut/spec/util/time.test.ts
✕ roundTime_locale
expect(received).toEqual(expected) // deep equality
Expected: 528526800000
Received: 528523200000
Test Suites: 1 failed, 1 total
Tests: 1 failed, 14 passed, 15 total

twin diff shows the two capsules differ only in the time zone:

$ twin diff kolkata.json new-york.json
2 differences: - kolkata.json + new-york.json
Environment variables (1)
TZ - Asia/Kolkata + America/New_York
Locale (1)
timeZone - Asia/Calcutta + America/New_York

twin rebuilt the reporter’s environment on a fresh Solari sandbox: Node 22.23.3 from nodejs.org, the exact commit fetched by SHA, npm 10.9.9, npm ci from the lockfile, and TZ=America/New_York. Both attempts failed with the same failure signature as the capsule.

replay.txt
Replay on solari (machine AlDq8esMs1x4vuzv…, run fd0c959a)
✓ prepare workspace 0.3s
✓ install node 22.23.3 2.1s
✓ check out 984bf46b2f90 4.5s
✓ install npm 10.9.9 3.7s
✓ install dependencies (npm ci) 46.4s
Attempts
1 FAIL exit 1 exit1:0aee0834ac9c4d2d 8.0s
2 FAIL exit 1 exit1:0aee0834ac9c4d2d 1.7s
expected FAIL exit1:0aee0834ac9c4d2d
REPRODUCED: every attempt failed exactly as the capsule recorded.

npm ci dominates setup (about 46 s). The first Jest run on a fresh machine takes 8 s and later runs about 1.7 s. That first run also prints a slow-file timing that later runs do not; twin ignores such timing annotations when fingerprinting failures, which is why both attempts match.

Opening the kept machine with twin shell --web gave a browser terminal in /tmp/twin/repo with TZ=America/New_York and Node 22.23.3, where the test fails with the issue’s exact numbers.

twin built the passing (Kolkata) environment once, confirmed it passes on the sandbox, applied the failing environment’s differences, and reported the minimal one.

bisect.txt
Bisect on solari (machine AvubsvqLazIY9m-5…, run 98e18221)
good environment built in 56.3s (5 steps)
Candidates (1)
TZ=America/New_York
Trials (2)
1 pass (good environment) 8.0s
2 FAIL TZ=America/New_York 1.7s
Minimal failing difference:
TZ=America/New_York
Applying it to the good environment fails the captured way; without it, it passes.

The two differences inspect listed (the TZ variable and the resolved zone) become one candidate: when the failing capsule has an explicit TZ value, the zone is varied through it.

The test builds its expected dates with a fixed UTC offset taken from January 1970:

// return timezone format like `'-06:00'` or `'+05:45'`
function getISOTimezone(): string {
const offsetMinutes = (new Date(0)).getTimezoneOffset();
...
}

That offset is wrong for October in any zone with daylight saving time, so the expected value is off by exactly one hour (528526800000 - 528523200000 = 3600000 ms). roundTime(..., false) itself works in local time and is correct.

fix.patch drops the offset: an ISO date-time string without one is parsed as local time, which is what roundTime(..., false) works in.

fix.patch (excerpt)
it('roundTime_locale', function () {
const timezoneStr = getISOTimezone();
const time1 = new Date(`1986-10-06T11:25:45.678${timezoneStr}`);
// A date-time string without an offset is local time, which is what roundTime
// uses when isUTC is false. A fixed offset would ignore daylight saving time.
const time1 = new Date('1986-10-06T11:25:45.678');
expect(roundTime(new Date(time1), 'year', false).getTime())
.toEqual(new Date(`1986-01-01T00:00:00.000${timezoneStr}`).getTime());
.toEqual(new Date('1986-01-01T00:00:00.000').getTime());

The patch passes locally under seven zones (New York, Berlin, Sydney, Kolkata, UTC, Chatham, São Paulo). twin verify then rebuilt the New York reporter’s environment on a fresh sandbox, applied the patch on top of the captured commit and ran the test. Nothing had to be pushed first.

verify.txt
Replay on solari (machine AkhV_ntXHJrec7VZ…, run e179d260)
✓ prepare workspace 0.3s
✓ install node 22.23.3 2.1s
✓ check out 984bf46b2f90 4.2s
✓ upload candidate fix 0.0s
✓ apply candidate fix 0.3s
✓ install npm 10.9.9 4.7s
✓ install dependencies (npm ci) 46.8s
Attempts
1 PASS 8.1s
2 PASS 1.7s
expected FAIL exit1:0aee0834ac9c4d2d
FIXED: the command passes in the reporter's environment with the fix applied.

The recorded header reads Replay on solari; the current code prints Verify on solari for verify runs.

mcp.txt records a session against twin mcp on Solari, driven over stdio by a scripted MCP client (test/e2e/mcp-session.ts), not an LLM. It makes the calls a coding agent would:

Call Result Time
inspect the New York capsule the summary above 0 s
replay with attempts: 1 REPRODUCED, machine kept 71 s
run the Jest command on the kept machine TZ=America/New_York node v22.23.3, in /tmp/twin/repo; ✕ roundTime_locale, Expected: 528526800000, Received: 528523200000 4 s
write_file fix.patch wrote 2874 bytes to /tmp/twin/repo/fix.patch 1 s
run git apply fix.patch and the test again ✓ roundTime_locale, Tests: 15 passed, 15 total 5 s
verify with the patch, attempts: 1 FIXED: the command passes in the reporter's environment with the fix applied. 74 s
release the kept machine released … 7 s

The verbatim excerpts are in the AI agents guide. With a key, rerun it from a clone with pnpm e2e:mcp.

  1. With a Solari key, from a clone of twin:

    export SOLARI_API_KEY=slr_live_...
    twin replay examples/echarts-21538/new-york.json
    twin bisect examples/echarts-21538/new-york.json --good examples/echarts-21538/kolkata.json
    twin verify examples/echarts-21538/new-york.json --patch examples/echarts-21538/fix.patch
  2. Without a key, against a local Docker container (development harness, same guest scripts):

    pnpm e2e:docker examples/echarts-21538/new-york.json
    pnpm e2e:docker:bisect examples/echarts-21538/new-york.json examples/echarts-21538/kolkata.json