@@ -484,6 +484,16 @@ export function generateDockerCompose(
484484 // Mount workspace directory at /host path for chroot
485485 agentVolumes . push ( `${ workspaceDir } :/host${ workspaceDir } :rw` ) ;
486486
487+ // Mount an empty writable home directory at /host$HOME
488+ // This gives tools a writable $HOME without exposing credential files.
489+ // The specific subdirectory mounts below (.cargo, .claude, etc.) overlay
490+ // on top, providing access to only the directories we explicitly mount.
491+ // Without this, $HOME inside the chroot is an empty root-owned directory
492+ // created by Docker as a side effect of subdirectory mounts, which causes
493+ // tools like rustc and Claude Code to hang or fail.
494+ const emptyHomeDir = path . join ( config . workDir , 'chroot-home' ) ;
495+ agentVolumes . push ( `${ emptyHomeDir } :/host${ effectiveHome } :rw` ) ;
496+
487497 // /tmp is needed for chroot mode to write:
488498 // - Temporary command scripts: /host/tmp/awf-cmd-$$.sh
489499 // - One-shot token LD_PRELOAD library: /host/tmp/awf-lib/one-shot-token.so
@@ -968,29 +978,26 @@ export async function writeConfigs(config: WrapperConfig): Promise<void> {
968978 logger . debug ( `MCP logs directory permissions fixed at: ${ mcpLogsDir } ` ) ;
969979 }
970980
971- // Ensure chroot home directory and subdirectories exist with correct ownership
972- // before Docker bind-mounts them. If a source directory doesn't exist, Docker
973- // creates it as root:root, making it inaccessible to the agent user (e.g., UID 1001).
974- // This is critical for CLI tools that need writable home subdirectories
975- // (e.g., Claude Code needs ~/.claude, npm needs ~/.npm).
976- // We also ensure $HOME itself has correct ownership because Docker's creation of
977- // subdirectory mounts creates parent directories as root:root, which prevents
978- // tools from creating new files/directories directly in $HOME.
981+ // Ensure chroot home subdirectories exist with correct ownership before Docker
982+ // bind-mounts them. If a source directory doesn't exist, Docker creates it as
983+ // root:root, making it inaccessible to the agent user (e.g., UID 1001).
984+ // Also create an empty writable home directory that gets mounted as $HOME
985+ // in the chroot, giving tools a writable home without exposing credentials.
979986 if ( config . enableChroot ) {
980987 const effectiveHome = getRealUserHome ( ) ;
981988 const uid = parseInt ( getSafeHostUid ( ) , 10 ) ;
982989 const gid = parseInt ( getSafeHostGid ( ) , 10 ) ;
983990
984- // Ensure $HOME exists and is owned by the agent user
985- // (Docker creates parent directories as root when bind-mounting subdirectories)
986- if ( fs . existsSync ( effectiveHome ) ) {
987- const stats = fs . statSync ( effectiveHome ) ;
988- if ( stats . uid !== uid || stats . gid !== gid ) {
989- fs . chownSync ( effectiveHome , uid , gid ) ;
990- logger . debug ( `Fixed ownership of ${ effectiveHome } to ${ uid } :${ gid } ` ) ;
991- }
991+ // Create empty writable home directory for the chroot
992+ // This is mounted as $HOME inside the container so tools can write to it
993+ const emptyHomeDir = path . join ( config . workDir , 'chroot-home' ) ;
994+ if ( ! fs . existsSync ( emptyHomeDir ) ) {
995+ fs . mkdirSync ( emptyHomeDir , { recursive : true } ) ;
992996 }
997+ fs . chownSync ( emptyHomeDir , uid , gid ) ;
998+ logger . debug ( `Created chroot home directory: ${ emptyHomeDir } (${ uid } :${ gid } )` ) ;
993999
1000+ // Ensure source directories for subdirectory mounts exist with correct ownership
9941001 const chrootHomeDirs = [
9951002 '.copilot' , '.cache' , '.config' , '.local' ,
9961003 '.anthropic' , '.claude' , '.cargo' , '.rustup' , '.npm' ,
@@ -1000,7 +1007,7 @@ export async function writeConfigs(config: WrapperConfig): Promise<void> {
10001007 if ( ! fs . existsSync ( dirPath ) ) {
10011008 fs . mkdirSync ( dirPath , { recursive : true } ) ;
10021009 fs . chownSync ( dirPath , uid , gid ) ;
1003- logger . debug ( `Created chroot home directory : ${ dirPath } (${ uid } :${ gid } )` ) ;
1010+ logger . debug ( `Created host home subdirectory : ${ dirPath } (${ uid } :${ gid } )` ) ;
10041011 }
10051012 }
10061013 }
0 commit comments