refactor(worker): delete per-job OpenCode container path, host_port split, and dead docker helpers
Claude-Session: https://claude.ai/code/session_01ScyZ1NhfN84LAnHpwhfEQk
This commit is contained in:
@@ -8,7 +8,6 @@ const mocks = vi.hoisted(() => ({
|
||||
Promise.resolve({ exitCode: 0, output: 'unexpected diff' }),
|
||||
),
|
||||
mutation: vi.fn(),
|
||||
stopWorkspaceContainer: vi.fn(() => Promise.resolve()),
|
||||
streamExecInContainer: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -23,7 +22,6 @@ vi.mock('../../src/env', () => ({
|
||||
env: {
|
||||
buildCreatedAt: 'test',
|
||||
buildSha: 'test',
|
||||
containerAccess: 'network',
|
||||
convexUrl: 'http://convex.test',
|
||||
jobTimeoutMs: 1000,
|
||||
runtime: 'docker',
|
||||
@@ -42,19 +40,11 @@ vi.mock('../../src/github', () => ({
|
||||
getInstallationToken: vi.fn(),
|
||||
openDraftPullRequest: vi.fn(),
|
||||
}));
|
||||
vi.mock('../../src/opencode-session', () => ({
|
||||
abortOpenCodeSession: vi.fn(),
|
||||
createOpenCodeSession: vi.fn(),
|
||||
promptOpenCodeSession: vi.fn(),
|
||||
replyOpenCodePermission: vi.fn(),
|
||||
}));
|
||||
vi.mock('../../src/runtime/docker', () => ({
|
||||
buildMarkedCommand: vi.fn(() => ['setsid', 'bash', '-lc', '# marker']),
|
||||
killBoxProcessesByMarker: vi.fn(() => Promise.resolve()),
|
||||
listWorkspaceContainerNames: vi.fn(() => Promise.resolve([])),
|
||||
runExecInContainer: vi.fn(),
|
||||
startWorkspaceContainer: vi.fn(),
|
||||
stopWorkspaceContainer: mocks.stopWorkspaceContainer,
|
||||
streamExecInContainer: mocks.streamExecInContainer,
|
||||
}));
|
||||
vi.mock('../../src/user-container', () => ({
|
||||
@@ -86,105 +76,85 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe('maintenance workspace cleanup', () => {
|
||||
test.each(['mark', 'container'] as const)(
|
||||
'releases and removes the workspace when %s teardown fails',
|
||||
async (failingStep) => {
|
||||
const worker = await load();
|
||||
const release = vi.fn();
|
||||
const close = vi.fn();
|
||||
const jobId = `maintenance-${failingStep}`;
|
||||
mocks.mutation.mockImplementation((reference) => {
|
||||
if (
|
||||
failingStep === 'mark' &&
|
||||
getFunctionName(reference) === 'agentJobs:markWorkspaceStopped'
|
||||
) {
|
||||
return Promise.reject(new Error('status teardown failed'));
|
||||
}
|
||||
return Promise.resolve('message-1');
|
||||
});
|
||||
mocks.stopWorkspaceContainer.mockImplementation(() =>
|
||||
failingStep === 'container'
|
||||
? Promise.reject(new Error('container teardown failed'))
|
||||
: Promise.resolve(),
|
||||
test('releases and removes the workspace when status teardown fails', async () => {
|
||||
const worker = await load();
|
||||
const release = vi.fn();
|
||||
const jobId = 'maintenance-mark';
|
||||
mocks.mutation.mockImplementation((reference) => {
|
||||
if (getFunctionName(reference) === 'agentJobs:markWorkspaceStopped') {
|
||||
return Promise.reject(new Error('status teardown failed'));
|
||||
}
|
||||
return Promise.resolve('message-1');
|
||||
});
|
||||
mocks.streamExecInContainer.mockImplementation(async (args) => {
|
||||
await args.onStdoutLine(
|
||||
JSON.stringify({
|
||||
type: 'item.completed',
|
||||
item: {
|
||||
type: 'agent_message',
|
||||
text: JSON.stringify(decision),
|
||||
},
|
||||
}),
|
||||
);
|
||||
mocks.streamExecInContainer.mockImplementation(async (args) => {
|
||||
await args.onStdoutLine(
|
||||
JSON.stringify({
|
||||
type: 'item.completed',
|
||||
item: {
|
||||
type: 'agent_message',
|
||||
text: JSON.stringify(decision),
|
||||
},
|
||||
}),
|
||||
);
|
||||
return { exitCode: 0, output: '' };
|
||||
});
|
||||
worker._setActiveWorkspaceForTests(jobId, {
|
||||
claim: {
|
||||
job: {
|
||||
_id: jobId as Id<'agentJobs'>,
|
||||
prompt: 'Review maintenance changes',
|
||||
jobType: 'maintenance_review',
|
||||
baseBranch: 'main',
|
||||
workBranch: 'spoon/maintenance',
|
||||
forkOwner: 'team',
|
||||
forkRepo: 'spoon',
|
||||
upstreamOwner: 'upstream',
|
||||
upstreamRepo: 'spoon',
|
||||
},
|
||||
spoon: { name: 'Spoon' },
|
||||
openai: {
|
||||
model: 'gpt-5',
|
||||
reasoningEffort: 'medium',
|
||||
},
|
||||
aiProviderProfile: {
|
||||
id: 'profile-1',
|
||||
name: 'Codex',
|
||||
provider: 'opencode_openai_login',
|
||||
authType: 'opencode_auth_json',
|
||||
model: 'gpt-5',
|
||||
reasoningEffort: 'medium',
|
||||
},
|
||||
github: {},
|
||||
secrets: [],
|
||||
return { exitCode: 0, output: '' };
|
||||
});
|
||||
worker._setActiveWorkspaceForTests(jobId, {
|
||||
claim: {
|
||||
job: {
|
||||
_id: jobId as Id<'agentJobs'>,
|
||||
prompt: 'Review maintenance changes',
|
||||
jobType: 'maintenance_review',
|
||||
baseBranch: 'main',
|
||||
workBranch: 'spoon/maintenance',
|
||||
forkOwner: 'team',
|
||||
forkRepo: 'spoon',
|
||||
upstreamOwner: 'upstream',
|
||||
upstreamRepo: 'spoon',
|
||||
},
|
||||
workdir: '/tmp/workspace',
|
||||
homeDir: '/tmp/workspace',
|
||||
username: 'tester',
|
||||
containerHome: '/home/tester',
|
||||
containerRepo: '/home/tester/Code/spoon',
|
||||
repoDir: '/tmp/workspace/Code/spoon',
|
||||
boxName: 'spoon-box-tester',
|
||||
boxHandle: { boxName: 'spoon-box-tester', release },
|
||||
githubToken: 'github-token',
|
||||
redact: (value: string) => value,
|
||||
runtime: 'codex',
|
||||
containerName: 'spoon-agent-job-maintenance',
|
||||
opencodeSession: {
|
||||
client: {} as never,
|
||||
sessionId: 'session-1',
|
||||
close,
|
||||
spoon: { name: 'Spoon' },
|
||||
openai: {
|
||||
model: 'gpt-5',
|
||||
reasoningEffort: 'medium',
|
||||
},
|
||||
});
|
||||
const consoleError = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => undefined);
|
||||
aiProviderProfile: {
|
||||
id: 'profile-1',
|
||||
name: 'Codex',
|
||||
provider: 'opencode_openai_login',
|
||||
authType: 'opencode_auth_json',
|
||||
model: 'gpt-5',
|
||||
reasoningEffort: 'medium',
|
||||
},
|
||||
github: {},
|
||||
secrets: [],
|
||||
},
|
||||
workdir: '/tmp/workspace',
|
||||
homeDir: '/tmp/workspace',
|
||||
username: 'tester',
|
||||
containerHome: '/home/tester',
|
||||
containerRepo: '/home/tester/Code/spoon',
|
||||
repoDir: '/tmp/workspace/Code/spoon',
|
||||
boxName: 'spoon-box-tester',
|
||||
boxHandle: { boxName: 'spoon-box-tester', release },
|
||||
githubToken: 'github-token',
|
||||
redact: (value: string) => value,
|
||||
runtime: 'codex',
|
||||
});
|
||||
const consoleError = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => undefined);
|
||||
|
||||
await expect(
|
||||
worker.sendWorkspaceMessage(jobId, 'Review maintenance changes'),
|
||||
).resolves.toBeUndefined();
|
||||
await expect(
|
||||
worker.sendWorkspaceMessage(jobId, 'Review maintenance changes'),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(release).toHaveBeenCalledTimes(1);
|
||||
expect(close).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.stopWorkspaceContainer).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.getWorktreeDiff).not.toHaveBeenCalled();
|
||||
expect(
|
||||
mocks.mutation.mock.calls.some(([, args]) => args.kind === 'diff'),
|
||||
).toBe(false);
|
||||
await expect(worker.stopWorkspace(jobId)).rejects.toThrow(
|
||||
'Agent workspace is not active on this worker.',
|
||||
);
|
||||
expect(consoleError).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
expect(release).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.getWorktreeDiff).not.toHaveBeenCalled();
|
||||
expect(
|
||||
mocks.mutation.mock.calls.some(([, args]) => args.kind === 'diff'),
|
||||
).toBe(false);
|
||||
await expect(worker.stopWorkspace(jobId)).rejects.toThrow(
|
||||
'Agent workspace is not active on this worker.',
|
||||
);
|
||||
expect(consoleError).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user