fix: harden managed OpenCode startup

This commit is contained in:
Bohdan Triapitsyn
2026-04-28 12:35:00 +03:00
parent ccab4f49df
commit 72e706972a
5 changed files with 201 additions and 19 deletions
@@ -90,6 +90,11 @@ const createRuntime = (overrides = {}) => {
clearResolvedOpenCodeBinary: vi.fn(),
buildAugmentedPath: vi.fn(() => '/home/user/.bun/bin:/usr/local/bin:/usr/bin'),
buildManagedOpenCodePath: vi.fn(() => '/home/user/.bun/bin:/usr/local/bin:/usr/bin'),
getManagedOpenCodeShellEnvSnapshot: vi.fn(() => ({
PATH: '/home/user/.bun/bin:/usr/local/bin:/usr/bin',
SHELL_ONLY: 'yes',
OPENCODE_SERVER_PASSWORD: 'shell-password',
})),
...overrides,
});
};
@@ -112,6 +117,7 @@ describe('OpenCode lifecycle', () => {
expect(binary).toBe('opencode');
expect(args).toEqual(['serve', '--hostname', '127.0.0.1', '--port', '45678']);
expect(options.env.PATH).toBe('/home/user/.bun/bin:/usr/local/bin:/usr/bin');
expect(options.env.SHELL_ONLY).toBe('yes');
expect(options.env.OPENCODE_SERVER_PASSWORD).toBe('password');
await server.close();
@@ -163,4 +169,51 @@ describe('OpenCode lifecycle', () => {
await server.close();
});
it('reports the exit signal when managed OpenCode exits before becoming ready', async () => {
delete process.env.OPENCODE_BINARY;
const firstChild = createMockChild();
const secondChild = createMockChild();
spawnMock.mockImplementationOnce(() => {
queueMicrotask(() => {
firstChild.emit('exit', null, 'SIGTERM');
});
return firstChild;
});
spawnMock.mockImplementationOnce(() => {
queueMicrotask(() => {
secondChild.emit('exit', null, 'SIGTERM');
});
return secondChild;
});
const runtime = createRuntime();
await expect(runtime.startOpenCode()).rejects.toThrow('OpenCode exited with signal SIGTERM. No stdout/stderr captured');
expect(spawnMock).toHaveBeenCalledTimes(2);
});
it('retries managed OpenCode startup once after a pre-ready exit', async () => {
delete process.env.OPENCODE_BINARY;
const firstChild = createMockChild();
const secondChild = createMockChild();
spawnMock.mockImplementationOnce(() => {
queueMicrotask(() => {
firstChild.emit('exit', null, 'SIGTERM');
});
return firstChild;
});
spawnMock.mockImplementationOnce(() => {
queueMicrotask(() => {
secondChild.stdout.emit('data', 'opencode server listening on http://127.0.0.1:45678\n');
});
return secondChild;
});
const runtime = createRuntime();
const server = await runtime.startOpenCode();
expect(spawnMock).toHaveBeenCalledTimes(2);
await server.close();
});
});