Files
Gabriel Brown 40a6dd78e4
Build and Push Spoon Images / quality (push) Successful in 1m47s
Build and Push Spoon Images / build-images (push) Successful in 6m33s
Fix worker image missing docker CLI; harden spawn-failure handling
Root cause of the prod empty-response: the spoon-agent-worker image shipped
without a docker CLI binary, so it could never launch the codex job container.
On Debian trixie (the bun base) 'docker.io' + --no-install-recommends installs
the daemon package but omits the client (split into 'docker-cli'), leaving no
'docker' on PATH. execa('docker', ...) hit ENOENT, and with reject:false that
resolves with exitCode undefined -> coerced to 0 -> looked like a successful
empty run -> 'Codex completed without producing an assistant response'.

- agent-worker.Dockerfile: drop docker.io, install the official static docker
  CLI client pinned to 29.5.3 (matches the host daemon) to /usr/local/bin/docker
- runtime/docker.ts: normalizeRunResult() so a spawn failure (exitCode null) is
  always a non-zero exit carrying the real reason, never a silent empty success
- tests: cover the spawn-failure and normal-result paths
2026-06-24 06:31:17 -04:00

70 lines
2.4 KiB
TypeScript

import { afterEach, describe, expect, test, vi } from 'vitest';
const loadVolumeSpec = async () => {
vi.resetModules();
process.env.SPOON_WORKER_TOKEN = 'test-worker-token';
process.env.GITHUB_APP_ID = '123';
process.env.GITHUB_APP_PRIVATE_KEY =
'-----BEGIN PRIVATE KEY-----\\ntest\\n-----END PRIVATE KEY-----';
return await import('../../src/runtime/docker');
};
describe('Docker runtime', () => {
afterEach(() => {
delete process.env.SPOON_AGENT_CONTAINER_RUNTIME;
delete process.env.SPOON_AGENT_CONTAINER_VOLUME_OPTIONS;
vi.resetModules();
});
test('adds SELinux relabel option for Podman workspace mounts by default', async () => {
process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'podman';
const { jobWorkspaceVolumeSpec } = await loadVolumeSpec();
expect(jobWorkspaceVolumeSpec('/tmp/spoon-job')).toBe(
'/tmp/spoon-job:/workspace:Z',
);
});
test('does not add Podman volume options for Docker by default', async () => {
process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'docker';
const { jobWorkspaceVolumeSpec } = await loadVolumeSpec();
expect(jobWorkspaceVolumeSpec('/tmp/spoon-job')).toBe(
'/tmp/spoon-job:/workspace',
);
});
test('allows explicit workspace mount options', async () => {
process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'podman';
process.env.SPOON_AGENT_CONTAINER_VOLUME_OPTIONS = 'z';
const { jobWorkspaceVolumeSpec } = await loadVolumeSpec();
expect(jobWorkspaceVolumeSpec('/tmp/spoon-job')).toBe(
'/tmp/spoon-job:/workspace:z',
);
});
test('treats a spawn failure (no exitCode) as a non-zero exit, not empty success', async () => {
const { normalizeRunResult } = await loadVolumeSpec();
// This is what execa returns with `reject: false` when the runtime binary is
// missing (e.g. no `docker` CLI in the worker image): exitCode is undefined.
const result = normalizeRunResult(
{ exitCode: undefined, shortMessage: 'spawn docker ENOENT' },
undefined,
(value) => value,
);
expect(result.exitCode).toBe(1);
expect(result.output).toContain('spawn docker ENOENT');
});
test('passes through a normal command result unchanged', async () => {
const { normalizeRunResult } = await loadVolumeSpec();
const result = normalizeRunResult(
{ exitCode: 0, shortMessage: undefined },
'hello',
(value) => value,
);
expect(result).toEqual({ exitCode: 0, output: 'hello' });
});
});