ci: split serialized tests into five shards (#10923)

## Thinking Path

> - Paperclip uses CI to keep control-plane changes safe and mergeable.
> - The PR workflow splits serialized server tests across isolated
runners.
> - A recent successful run spent 305 seconds in serialized shard 2/4.
> - That job was the slowest check in the run.
> - The four shards reported about 739 seconds of Vitest suite time.
> - This pull request adds a fifth serialized shard and keeps release
verification aligned.
> - The benefit is a shorter PR critical path with no loss of test
coverage.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The PR and release verification workflows run serialized server tests in
four shards.

**Current behavior**

Successful PR run 30876682788 spent 305 seconds in `Verify serialized
server suites (2/4)`. The test step used 256 seconds and made this job
the slowest check.

**Proposed behavior**

Run the same serialized suite set in five complete and non-overlapping
shards.

**Reason and benefit**

The measured suites reported about 739 seconds of total Vitest time.
Five runners reduce the expected average suite time from about 185
seconds to about 148 seconds before setup overhead.

**Breaking changes**

None. The change only alters CI partition size.

## What Changed

- Split serialized server tests into five shards in the PR workflow.
- Apply the same five-shard layout to release verification.
- Add a partition test that proves complete and non-overlapping
serialized coverage.
- Update release workflow coverage tests for five shards.

## Verification

- `node --test scripts/__tests__/run-vitest-stable-shard.test.mjs
scripts/__tests__/release-verify-workflow.test.mjs`
- `git diff --check`

## Risks

- Low risk. CI uses one additional runner for the serialized lane.
- Round-robin partition weights can still vary as suite timings change.

> This change does not overlap with planned core work in `ROADMAP.md`.
Related PR #10663 optimized the separate general-server lane.

## Model Used

- OpenAI Codex, GPT-5, agentic coding with reasoning, tool use, and code
execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Devin Foley <139239+devinfoley@users.noreply.github.com>
This commit is contained in:
Devin Foley 2026-08-05 16:29:49 -07:00 committed by GitHub
parent 72b509c895
commit b6e58019f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 40 additions and 18 deletions

View File

@ -270,18 +270,25 @@ jobs:
fail-fast: false
matrix:
include:
# A successful PR run on 2026-08-04 (30876682788) spent 256s in
# serialized shard 2/4, making its 305s job the run's slowest check.
# Five shards reduce the measured 739s suite total to about 148s per
# runner before setup overhead.
- shard_index: 0
shard_count: 4
shard_label: 1/4
shard_count: 5
shard_label: 1/5
- shard_index: 1
shard_count: 4
shard_label: 2/4
shard_count: 5
shard_label: 2/5
- shard_index: 2
shard_count: 4
shard_label: 3/4
shard_count: 5
shard_label: 3/5
- shard_index: 3
shard_count: 4
shard_label: 4/4
shard_count: 5
shard_label: 4/5
- shard_index: 4
shard_count: 5
shard_label: 5/5
steps:
- name: Checkout repository

View File

@ -109,17 +109,20 @@ jobs:
matrix:
include:
- shard_index: 0
shard_count: 4
shard_label: 1/4
shard_count: 5
shard_label: 1/5
- shard_index: 1
shard_count: 4
shard_label: 2/4
shard_count: 5
shard_label: 2/5
- shard_index: 2
shard_count: 4
shard_label: 3/4
shard_count: 5
shard_label: 3/5
- shard_index: 3
shard_count: 4
shard_label: 4/4
shard_count: 5
shard_label: 4/5
- shard_index: 4
shard_count: 5
shard_label: 5/5
steps:
- name: Checkout repository

View File

@ -43,8 +43,8 @@ test("release verify workflow covers the same split test surface as stable PR ve
);
}
for (const shardIndex of [0, 1, 2, 3]) {
assert.match(verifyWorkflow, new RegExp(`shard_index: ${shardIndex}[\\s\\S]*?shard_count: 4`));
for (const shardIndex of [0, 1, 2, 3, 4]) {
assert.match(verifyWorkflow, new RegExp(`shard_index: ${shardIndex}[\\s\\S]*?shard_count: 5`));
}
assert.match(verifyWorkflow, /pnpm test:run:general -- --group/);

View File

@ -29,6 +29,18 @@ function dryRunJson(args) {
}
const SHARD_COUNT = 4;
const SERIALIZED_SHARD_COUNT = 5;
test("the serialized shards form a complete, non-overlapping partition", () => {
const shards = Array.from({ length: SERIALIZED_SHARD_COUNT }, (_, index) =>
dryRunJson(["--mode", "serialized", "--shard-index", String(index), "--shard-count", String(SERIALIZED_SHARD_COUNT)]),
);
const total = shards[0].serializedSuiteCount;
const selected = shards.flatMap((shard) => shard.selectedSerializedSuites);
assert.equal(selected.length, total, "every serialized suite must be selected exactly once");
assert.equal(new Set(selected).size, total, "serialized shards must not overlap");
});
test("the general-server shards form a complete, non-overlapping partition", () => {
const shards = Array.from({ length: SHARD_COUNT }, (_, index) =>