Skip to content

Commit b1ceed6

Browse files
committed
test(e2e): retry session.create through server session-route warmup
Under the heaviest local parallel run, session.create can return an empty body (no res.data) even though waitForReady already saw the server answer /doc: the HTTP listener is up but the session route is still warming, so the very first create right after boot transiently fails with empty stderr/stdout. This is harness readiness gating, not a product retry — no session exists yet, so nothing under test has executed. Retry create up to 5 times with linear backoff; a server that is genuinely down still throws with captured output after the attempts are exhausted. Shared by createSession and createChildSession.
1 parent c817f8a commit b1ceed6

1 file changed

Lines changed: 37 additions & 16 deletions

File tree

packages/e2e-tests/src/harness.ts

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,38 @@ export class TestHarness {
9898

9999
/** Create a session bound to the isolated workdir. Throws on failure. */
100100
async createSession(): Promise<string> {
101-
const res = await this.client.session.create({
102-
query: { directory: this.opencode.env.workdir },
103-
});
104-
if (!res.data) {
101+
return this.createSessionWithRetry(
102+
() => this.client.session.create({ query: { directory: this.opencode.env.workdir } }),
103+
"session.create",
104+
);
105+
}
106+
107+
/**
108+
* Post a session.create and retry the transient warmup failure where the
109+
* server reports ready on `/doc` but its session route briefly returns an
110+
* empty body under load (no `res.data`). This is harness readiness gating,
111+
* not a product retry: no session exists yet, so nothing under test has run.
112+
* A persistent failure (server actually down) still throws with captured
113+
* stderr/stdout after the attempts are exhausted.
114+
*/
115+
private async createSessionWithRetry(
116+
attempt: () => Promise<{ data?: { id: string } | null }>,
117+
label: string,
118+
): Promise<string> {
119+
const maxAttempts = 5;
120+
for (let i = 1; i <= maxAttempts; i++) {
121+
const res = await attempt();
122+
if (res.data) return res.data.id;
123+
if (i < maxAttempts) {
124+
await Bun.sleep(200 * i);
125+
continue;
126+
}
105127
throw new Error(
106-
`session.create failed. stderr:\n${this.opencode.stderr()}\nstdout:\n${this.opencode.stdout()}`,
128+
`${label} failed after ${maxAttempts} attempts. stderr:\n${this.opencode.stderr()}\nstdout:\n${this.opencode.stdout()}`,
107129
);
108130
}
109-
return res.data.id;
131+
// Unreachable: the loop either returns an id or throws.
132+
throw new Error(`${label} failed`);
110133
}
111134

112135
/**
@@ -120,16 +143,14 @@ export class TestHarness {
120143
* nudges, no §N§ prefix injection.
121144
*/
122145
async createChildSession(parentId: string, title?: string): Promise<string> {
123-
const res = await this.client.session.create({
124-
query: { directory: this.opencode.env.workdir },
125-
body: { parentID: parentId, ...(title ? { title } : {}) },
126-
});
127-
if (!res.data) {
128-
throw new Error(
129-
`child session.create failed. stderr:\n${this.opencode.stderr()}\nstdout:\n${this.opencode.stdout()}`,
130-
);
131-
}
132-
return res.data.id;
146+
return this.createSessionWithRetry(
147+
() =>
148+
this.client.session.create({
149+
query: { directory: this.opencode.env.workdir },
150+
body: { parentID: parentId, ...(title ? { title } : {}) },
151+
}),
152+
"child session.create",
153+
);
133154
}
134155

135156
/**

0 commit comments

Comments
 (0)