Skip to content
Closed
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
93 changes: 36 additions & 57 deletions src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,24 @@ export class ContainerManager {
private async ensureImage(): Promise<void> {
const imageName = this.config.dockerImage || "claude-code-sandbox:latest";

// Check if image already exists
// If a custom dockerfile is specified, always rebuild to ensure changes are applied
if (this.config.dockerfile) {
console.log(chalk.blue(`• Building image from custom Dockerfile: ${imageName}...`));
await this.buildImage(this.config.dockerfile, imageName);
return;
}

// For default builds, check if image already exists
try {
await this.docker.getImage(imageName).inspect();
console.log(chalk.green(`✓ Using existing image: ${imageName}`));
return;
} catch (error) {
console.log(chalk.blue(`• Building image: ${imageName}...`));
console.log(chalk.blue(`• Building default image: ${imageName}...`));
}

// Check if we need to build from Dockerfile
if (this.config.dockerfile) {
await this.buildImage(this.config.dockerfile, imageName);
} else {
// Use default Dockerfile
await this.buildDefaultImage(imageName);
}
// Use default Dockerfile
await this.buildDefaultImage(imageName);
}

private async buildDefaultImage(imageName: string): Promise<void> {
Expand Down Expand Up @@ -502,18 +504,6 @@ exec claude --dangerously-skip-permissions' > /start-claude.sh && \\
const { execSync } = require("child_process");
const fs = require("fs");

// Helper function to get tar flags safely
const getTarFlags = () => {
try {
// Test if --no-xattrs is supported by checking tar help
execSync("tar --help 2>&1 | grep -q no-xattrs", { stdio: "pipe" });
return "--no-xattrs";
} catch {
// --no-xattrs not supported, use standard tar
return "";
}
};

try {
// Get list of git-tracked files (including uncommitted changes)
const trackedFiles = execSync("git ls-files", {
Expand Down Expand Up @@ -590,19 +580,18 @@ exec claude --dangerously-skip-permissions' > /start-claude.sh && \\
// Also copy .git directory to preserve git history
console.log(chalk.blue("• Copying git history..."));
const gitTarFile = `/tmp/claude-sandbox-git-${Date.now()}.tar`;
// Exclude macOS resource fork files and .DS_Store when creating git archive
// Also strip extended attributes to prevent macOS xattr issues in Docker
const tarFlags = getTarFlags();
// On macOS, also exclude extended attributes that cause Docker issues
const additionalFlags = (process.platform as string) === "darwin" ? "--no-xattrs --no-fflags" : "";
const combinedFlags = `${tarFlags} ${additionalFlags}`.trim();
execSync(
`tar -cf "${gitTarFile}" --exclude="._*" --exclude=".DS_Store" ${combinedFlags} .git`,
{
cwd: workDir,
stdio: "pipe",
},
);

// On macOS, use --no-xattrs and --no-fflags to prevent extended attribute issues
let tarCommand = `tar -cf "${gitTarFile}" --exclude="._*" --exclude=".DS_Store"`;
if (process.platform === "darwin") {
tarCommand += " --no-xattrs --no-fflags";
}
tarCommand += " .git";

execSync(tarCommand, {
cwd: workDir,
stdio: "pipe",
});

try {
const gitStream = fs.createReadStream(gitTarFile);
Expand Down Expand Up @@ -636,18 +625,6 @@ exec claude --dangerously-skip-permissions' > /start-claude.sh && \\
const path = require("path");
const { execSync } = require("child_process");

// Helper function to get tar flags safely
const getTarFlags = () => {
try {
// Test if --no-xattrs is supported by checking tar help
execSync("tar --help 2>&1 | grep -q no-xattrs", { stdio: "pipe" });
return "--no-xattrs";
} catch {
// --no-xattrs not supported, use standard tar
return "";
}
};

try {
// First, try to get credentials from macOS Keychain if on Mac
if (process.platform === "darwin") {
Expand Down Expand Up @@ -779,24 +756,26 @@ exec claude --dangerously-skip-permissions' > /start-claude.sh && \\

// Copy .claude directory if it exists (but skip if we already copied from Keychain)
const claudeDir = path.join(os.homedir(), ".claude");
const skipClaudeDir = process.platform === "darwin";
if (
fs.existsSync(claudeDir) &&
fs.statSync(claudeDir).isDirectory() &&
process.platform !== "darwin"
!skipClaudeDir
) {
console.log(chalk.blue("• Copying .claude directory..."));

const tarFile = `/tmp/claude-dir-${Date.now()}.tar`;
const tarFlags = getTarFlags();
// On macOS, also exclude extended attributes that cause Docker issues
const additionalFlags = (process.platform as string) === "darwin" ? "--no-xattrs --no-fflags" : "";
const combinedFlags = `${tarFlags} ${additionalFlags}`.trim();
execSync(
`tar -cf "${tarFile}" ${combinedFlags} -C "${os.homedir()}" .claude`,
{
stdio: "pipe",
},
);

// On macOS, use --no-xattrs and --no-fflags to prevent extended attribute issues
let tarCommand = `tar -cf "${tarFile}"`;
if (process.platform === "darwin") {
tarCommand += " --no-xattrs --no-fflags";
}
tarCommand += ` -C "${os.homedir()}" .claude`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: MacOS Tar Flags Unreachable

The macOS-specific tar flags (--no-xattrs --no-fflags) for copying the .claude directory are unreachable. The if (process.platform === 'darwin') check is nested within a block that is explicitly skipped on macOS, making this logic dead code.

Fix in Cursor Fix in Web


execSync(tarCommand, {
stdio: "pipe",
});

const stream = fs.createReadStream(tarFile);
await container.putArchive(stream, {
Expand Down