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 |
Summary
Section titled “Summary”| 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 |
1. Capture
Section titled “1. Capture”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.tsTZ=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 totaltwin diff shows the two capsules differ only in the time zone:
$ twin diff kolkata.json new-york.json2 differences: - kolkata.json + new-york.json
Environment variables (1) TZ - Asia/Kolkata + America/New_York
Locale (1) timeZone - Asia/Calcutta + America/New_York2. Replay: 72 s
Section titled “2. Replay: 72 s”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 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.
3. Bisect: 69 s
Section titled “3. Bisect: 69 s”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 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_YorkApplying 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.
4. The cause
Section titled “4. The cause”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.
5. Verify: 71 s
Section titled “5. Verify: 71 s”fix.patch drops the offset: an ISO date-time string without one is parsed as local time, which is what roundTime(..., false) works in.
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.
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.
6. The same loop through MCP: 162 s
Section titled “6. The same loop through MCP: 162 s”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.
Run it yourself
Section titled “Run it yourself”-
With a Solari key, from a clone of twin:
export SOLARI_API_KEY=slr_live_...twin replay examples/echarts-21538/new-york.jsontwin bisect examples/echarts-21538/new-york.json --good examples/echarts-21538/kolkata.jsontwin verify examples/echarts-21538/new-york.json --patch examples/echarts-21538/fix.patch -
Without a key, against a local Docker container (development harness, same guest scripts):
pnpm e2e:docker examples/echarts-21538/new-york.jsonpnpm e2e:docker:bisect examples/echarts-21538/new-york.json examples/echarts-21538/kolkata.json