Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/workdir-setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jest.mock('./host-env', () => require('./test-helpers/fs-mock-factory.test-utils
// eslint-disable-next-line @typescript-eslint/no-require-imports
jest.mock('./host-identity', () => require('./test-helpers/fs-mock-factory.test-utils').hostIdentityMockFactory());

const actualFs = jest.requireActual<typeof import('fs')>('fs');

import { prepareWorkDirectories, workdirSetupTestHelpers } from './workdir-setup';
import { resolveLogPaths } from './log-paths';
import { getRealUserHome } from './host-identity';
Expand Down Expand Up @@ -138,6 +140,41 @@ describe('prepareWorkDirectories', () => {
expect(mode).toBe(0o777);
});

it('does not throw when chmod on pre-existing mcp-logs dir fails with EPERM', () => {
const mcpLogsDir = '/tmp/gh-aw/mcp-logs';
fs.mkdirSync(mcpLogsDir, { recursive: true, mode: 0o700 });

// Simulate EPERM: directory owned by another user (e.g., MCP gateway)
const eperm = new Error("EPERM: operation not permitted, chmod '/tmp/gh-aw/mcp-logs'") as NodeJS.ErrnoException;
eperm.code = 'EPERM';
(fs.chmodSync as jest.Mock).mockImplementation((target: fs.PathLike, mode: fs.Mode) => {
if (target === mcpLogsDir) throw eperm;
actualFs.chmodSync(target, mode);
});

const config = buildConfig();
const logPaths = resolveLogPaths(config);

expect(() => prepareWorkDirectories(config, logPaths)).not.toThrow();
});

it('rethrows non-EPERM errors when chmod on pre-existing mcp-logs dir fails', () => {
const mcpLogsDir = '/tmp/gh-aw/mcp-logs';
fs.mkdirSync(mcpLogsDir, { recursive: true, mode: 0o700 });

const erofs = new Error("EROFS: read-only file system, chmod '/tmp/gh-aw/mcp-logs'") as NodeJS.ErrnoException;
erofs.code = 'EROFS';
(fs.chmodSync as jest.Mock).mockImplementation((target: fs.PathLike, mode: fs.Mode) => {
if (target === mcpLogsDir) throw erofs;
actualFs.chmodSync(target, mode);
});

const config = buildConfig();
const logPaths = resolveLogPaths(config);

expect(() => prepareWorkDirectories(config, logPaths)).toThrow(erofs);
});

it('falls back to world-writable squid logs when squid chown fails', () => {
const proxyLogsDir = path.join(fixture.tempDir, 'proxy-logs');
(fs.chownSync as unknown as jest.Mock).mockImplementation((targetPath: fs.PathLike) => {
Expand Down
14 changes: 11 additions & 3 deletions src/workdir-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,17 @@ function prepareLogDirectories(logPaths: LogPaths): void {
fs.chmodSync(mcpLogsDir, 0o777);
logger.debug(`MCP logs directory created at: ${mcpLogsDir}`);
} else {
// Fix permissions if directory already exists (e.g., created by a previous run)
fs.chmodSync(mcpLogsDir, 0o777);
logger.debug(`MCP logs directory permissions fixed at: ${mcpLogsDir}`);
// Best-effort permission fix if directory already exists (e.g., created by MCP gateway
// or a previous run). May fail with EPERM if owned by a different user.
try {
fs.chmodSync(mcpLogsDir, 0o777);
logger.debug(`MCP logs directory permissions fixed at: ${mcpLogsDir}`);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'EPERM') {
throw error;
}
logger.debug(`MCP logs directory already exists at: ${mcpLogsDir} (chmod skipped, owned by another user)`);
}
}
}

Expand Down
Loading