v1.91.121.0 test(windows-seed): refresh the Windows duration seed from real windows-latest timings (#3243)

* fix(windows-seed): record slow Windows files in full instead of at the 6-minute deadline

The first Windows recording on eca247e99 (run 39008249142) killed
test/gstack-doctor.test.ts at the 360 s per-file deadline and would have
seeded it at 360 s, although it takes about 484 s on Windows. --record-durations
now honors an explicit --wall-timeout as its per-file deadline, and the
windows-record-durations job passes --wall-timeout 1200 (inside its 30-minute
job limit). The shard job's 15-minute timeout and the shard count are unchanged.

The recording job also uploads its full per-file logs, so a file that fails
while recording (browse/test/cookie-import-native-job.test.ts did in that run)
can be diagnosed.

Intentional pin change: test/windows-native-workflows.test.ts now expects
the --wall-timeout 1200 recording command and the log upload step.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(windows-seed): refresh the Windows free-suite duration seed from a windows-latest recording

Recorded by the windows-record-durations job (run 39009112245, recorder exit 0,
no failed or timed-out files) at 31150049, with the full per-file deadline:
638 -> 738 durations (106 added, 6 removed). test/gstack-doctor.test.ts 392 s
and test/hook-scripts.test.ts 135 s were unseeded;
browse/test/cookie-import-native-job.test.ts 99 s -> 118 s. With the new
seed the six-shard plan isolates gstack-doctor in its own shard (392 s
predicted) and puts the other two in separate shards (265 s each), where the
old seed packed cookie-import-native-job into a 121-file shard.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): Windows seed refresh entry

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore(release): stamp v1.91.121.0

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-10-17 06:44:32 +00:00
1 parent eca247e99a
commit d258bcffb0
9 files changed
+784 -645

No files matched your search

+11 -1
View File
@@ -235,11 +235,21 @@ jobs:
env:
TEMP: ${{ runner.temp }}
TMP: ${{ runner.temp }}
run: bun run test:windows --record-durations
run: bun run test:windows --record-durations --wall-timeout 1200
- name: Name the recording as the Windows seed
if: always()
shell: bash
run: cp scripts/free-test-durations.json "$RUNNER_TEMP/free-test-durations-windows.json"
- name: Upload full recording logs
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
with:
name: windows-record-durations-logs
path: |
.context/free-test-logs/gstack-free-test-*.log
${{ runner.temp }}/gstack-free-test-*.log
include-hidden-files: true
if-no-files-found: ignore
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
if: always()
with:
+11
View File
@@ -1,5 +1,16 @@
# Changelog
## [1.91.121.0] - 2026-10-17
**Windows free-test shards stop running out of time on files the Windows seed didn't know.** The Windows duration seed dated from 2026-10-04 and had no entry for `test/gstack-doctor.test.ts` (about 6.5 minutes on Windows) or `test/hook-scripts.test.ts`. The packer guessed, put `cookie-import-native-job` into a 121-file shard, and that shard hit the 15-minute job limit.
### Fixed
- **Windows duration seed refreshed from a real `windows-latest` recording.** `scripts/free-test-durations-windows.json` goes from 638 to 738 entries. The six-shard plan now gives `gstack-doctor` a shard of its own and puts the other slow files in separate shards. The shard job's timeout and shard count are unchanged.
- **`--record-durations` honors `--wall-timeout`.** The recorder used to kill any file at 360 s and record that deadline as its duration. The Windows recording job now passes `--wall-timeout 1200` and uploads its full per-file logs.
### If you…
- **…refresh the Windows seed:** run `gh workflow run windows-free-tests.yml --ref <branch> -f record_durations=true`, check that the job exited 0, then commit the `free-test-durations-windows` artifact. A file that fails while recording now leaves its log in `windows-record-durations-logs`.
## [1.91.120.0] - 2026-10-17
**The Windows doctor test no longer times out.** The `windows bash` test from v1.91.117.0 ran five full `gstack-doctor` passes in one test. Each pass takes up to about 6 s on a Windows runner, so the test took 20 to 30 s against the 30 s per-test limit, and it timed out on one pull request run.
+1 -1
View File
@@ -1 +1 @@
1.91.120.0
1.91.121.0
+1 -1
View File
@@ -1,4 +1,4 @@
# gstack digest v1.91.120.0 — regenerate/re-copy after upgrading gstack
# gstack digest v1.91.121.0 — regenerate/re-copy after upgrading gstack
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
for agent hosts without a full skill install. The full skills add workflows,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gstack",
"version": "1.91.120",
"version": "1.91.121",
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
"license": "MIT",
"type": "module",
File diff suppressed because it is too large. Load diff
+11 -3
View File
@@ -1707,13 +1707,21 @@ export function exitCodeFor(status: FreeShardStatus): number {
return status === 'timed-out' ? 124 : 1;
}
/** Per-file deadline for `--record-durations`: an explicit --wall-timeout, else the one-file default. */
export function recordingWallTimeoutMs(options: Pick<CliOptions, 'wallTimeoutExplicit' | 'wallTimeoutMs'>): number {
return options.wallTimeoutExplicit ? options.wallTimeoutMs : wallTimeoutForShard(1);
}
/**
* `--record-durations`: time every file in its own child (exact per-file wall,
* immune to bun's stream buffering) and write the committed seed atomically.
* Uses the same isolated, strictly classified children as a normal run.
* Run against an immutable checkout; never time while editing its inputs.
* An explicit --wall-timeout sets the per-file deadline, so a file slower than
* the default deadline is timed in full instead of being killed and recorded
* at the deadline.
*/
async function recordFreeTestDurations(files: string[], jobs: number): Promise<number> {
async function recordFreeTestDurations(files: string[], jobs: number, wallTimeoutMs: number): Promise<number> {
const durations: Record<string, number> = {};
const failed: string[] = [];
const expectedCount = files.length;
@@ -1722,7 +1730,7 @@ async function recordFreeTestDurations(files: string[], jobs: number): Promise<n
// As in full-suite mode, finish parallel work before exclusive host-state fixtures.
await forEachFileAlone(files.filter(file => !(file in TREE_MUTATING)), files.filter(file => file in TREE_MUTATING), jobs, async (file, index) => {
const outcome = await runFreeShard([file], index + 1, files.length, {
wallTimeoutMs: wallTimeoutForShard(1), quiet: true, homeGuard: sharedFreeHome,
wallTimeoutMs, quiet: true, homeGuard: sharedFreeHome,
});
durations[normalizeRelativePath(file)] = outcome.elapsedMs;
if (outcome.status !== 'passed') failed.push(file);
@@ -1907,7 +1915,7 @@ async function main(): Promise<number> {
}
if (options.recordDurations) {
return recordFreeTestDurations(files, fullSuiteJobs());
return recordFreeTestDurations(files, fullSuiteJobs(), recordingWallTimeoutMs(options));
}
if (options.attributeHome) return attributeFreeHomeWriters(files.filter(file => !(file in TREE_MUTATING)), files.filter(file => file in TREE_MUTATING),
fullSuiteJobs(), (file, index, homeGuard) => runFreeShard([file], index + 1, files.length, { wallTimeoutMs: wallTimeoutForShard(1), quiet: true, log: () => {}, homeGuard }));
+7
View File
@@ -18,6 +18,8 @@ import {
DEFAULT_WALL_TIMEOUT_MS,
PER_FILE_WALL_MS,
wallTimeoutForShard,
recordingWallTimeoutMs,
parseCliOptions,
KNOWN_WINDOWS_INCOMPATIBLE,
TEST_ROOTS,
TREE_MUTATING,
@@ -940,6 +942,11 @@ describe('test-free-shards: wall-timeout scaling', () => {
test('an explicit base above the scaled value wins', () => {
expect(wallTimeoutForShard(10, 10 * 60_000)).toBe(10 * 60_000);
});
test('--record-durations times each file under the one-file default unless --wall-timeout is explicit', () => {
expect(recordingWallTimeoutMs(parseCliOptions(['--record-durations']))).toBe(wallTimeoutForShard(1));
expect(recordingWallTimeoutMs(parseCliOptions(['--record-durations', '--wall-timeout', '1200']))).toBe(1_200_000);
});
});
+4 -1
View File
@@ -62,11 +62,14 @@ test('a record_durations dispatch times each Windows-safe file alone and uploads
expect(job.if).toBe('${{ inputs.record_durations }}');
expect(job['runs-on']).toBe('windows-latest');
const record = job.steps.find((step: any) => step.name === 'Time every Windows-safe file alone');
expect(record.run).toBe('bun run test:windows --record-durations');
expect(record.run).toBe('bun run test:windows --record-durations --wall-timeout 1200');
expect(record.env.GSTACK_FREE_TEST_DURATIONS).toBeUndefined();
const rename = job.steps.find((step: any) => step.name === 'Name the recording as the Windows seed');
expect(rename.if).toBe('always()');
expect(rename.run).toBe('cp scripts/free-test-durations.json "$RUNNER_TEMP/free-test-durations-windows.json"');
const logs = job.steps.find((step: any) => step.name === 'Upload full recording logs');
expect(logs.if).toBe('always()');
expect(logs.with['include-hidden-files']).toBe(true);
expect(job.steps.at(-1).with.name).toBe('free-test-durations-windows');
});