feat(worker): run terminals and agent turns as the box user

This commit is contained in:
Gabriel Brown
2026-07-13 10:04:14 -04:00
parent a956d63e16
commit 58bd101793
13 changed files with 263 additions and 22 deletions
@@ -1,7 +1,6 @@
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { afterEach, describe, expect, test, vi } from 'vitest';
import type { NormalizedAgentEvent } from '../../src/agent-events';
@@ -98,7 +97,9 @@ describe('ClaudeCodeAdapter prepareAuth', () => {
tempDirs.push(homeDir);
const adapter = createClaudeAdapter();
await adapter.prepareAuth(makeOAuthWorkspace(homeDir, 'anthropic_oauth_json'));
await adapter.prepareAuth(
makeOAuthWorkspace(homeDir, 'anthropic_oauth_json'),
);
const credentialsPath = path.join(homeDir, '.claude', '.credentials.json');
const contents = await readFile(credentialsPath, 'utf8');
@@ -111,7 +112,9 @@ describe('ClaudeCodeAdapter prepareAuth', () => {
tempDirs.push(homeDir);
const adapter = createClaudeAdapter();
await adapter.prepareAuth(makeOAuthWorkspace(homeDir, 'opencode_auth_json'));
await adapter.prepareAuth(
makeOAuthWorkspace(homeDir, 'opencode_auth_json'),
);
const credentialsPath = path.join(homeDir, '.claude', '.credentials.json');
await expect(stat(credentialsPath)).rejects.toThrow();
@@ -174,4 +177,16 @@ describe('ClaudeCodeAdapter', () => {
expect(command[3]).toContain("'claude' '-p'");
expect(command[3]).toContain("'--output-format' 'stream-json'");
});
test('execs the turn as the box user, not root', async () => {
const { createClaudeAdapter } = await loadAdapter();
let capturedUser: string | undefined;
const execStream = vi.fn((args: { user?: string }) => {
capturedUser = args.user;
return Promise.resolve({ exitCode: 0, output: '' });
}) as unknown as ExecStreamFn;
const adapter = createClaudeAdapter({ execStream });
await adapter.runTurn(makeWorkspace(), 'do it', () => Promise.resolve());
expect(capturedUser).toBe('tester');
});
});
@@ -22,6 +22,20 @@ describe('box registry', () => {
vi.clearAllMocks();
});
test('passes an onCreated hook so a fresh box gets its user initialized', async () => {
const module = await load();
const docker = await import('../../src/runtime/docker');
await module.acquireUserBox({
username: 'ada',
workdir: '/w',
containerHome: '/home/ada',
});
const call = vi.mocked(docker.ensureUserContainer).mock.calls[0]?.[0] as {
onCreated?: unknown;
};
expect(call.onCreated).toBeTypeOf('function');
});
test('re-ensures the container on every acquire so an externally removed box self-heals', async () => {
const module = await load();
const docker = await import('../../src/runtime/docker');