# Fix pi container repo mount and npm update permissions ## Problem After implementing configurable tool container home directories, new `pi-agent` containers still bind-mount the git repository at `/workspace` instead of under `/home/user/{repo_name}`. In addition, users cannot run `npm update -g @earendil-works/pi-coding-agent` inside the container because the global npm prefix (`/usr/lib/node_modules`) is owned by root. ## Root cause 1. The built-in `pi-agent` manifest in `tool_definition_manifests` still declares an explicit repo mount with `"target": "/workspace"` and `"working_dir": "/workspace"`. This masks the generated `/workspace → /home/user/{repo}` compatibility symlink. 2. `manifest_compiler.py` does not substitute the instance-specific `{{WORKSPACE_NAME}}` placeholder in explicit mount targets, and `instance_service.py` does not pass `WORKSPACE_NAME`/`REPO_NAME` to `compile_compose` for manifest-based tools. 3. The generated entrypoint hardcodes the literal string `{{WORKSPACE_NAME}}` as the symlink target. 4. `npm_global` packages are installed with `RUN npm install -g ...` as root into the system npm prefix, so the non-root container user cannot update them. 5. Once the repo mount moves out of `/workspace`, the generated `/workspace` compatibility symlink is created in the image as root. The non-root entrypoint cannot replace it (write permission is required on `/`), so container startup fails. 6. Older images baked a literal `{{WORKSPACE_NAME}}` directory into `/home/user`, which survives alongside the real repo-named mount directory. ## Fix 1. Add an Alembic data migration that updates the built-in `pi-agent` manifest: - Change the repo mount target to `~/{{WORKSPACE_NAME}}`. - Keep `runtime.working_dir` as `/workspace` (the compatibility symlink). - Update the startup script to chown the real mount path (`$HOME/$WORKSPACE_NAME`). 2. Update `manifest_compiler.py`: - Substitute `{{WORKSPACE_NAME}}` in mount targets in `compile_compose`. - Pass `WORKSPACE_NAME` as a container environment variable. - Generate the entrypoint symlink from the runtime `WORKSPACE_NAME` environment variable. - Install `npm_global` packages into a user-writable prefix (`{home_dir}/.npm-global`) and add it to `PATH`. - Use `sudo` or root to create the `/workspace` compatibility symlink, because `/` is owned by root and the non-root entrypoint cannot replace a root-owned symlink. - Start the container as root and drop privileges to the container user inside the entrypoint via `su`. - Do not create mount target directories or the `/workspace` symlink in the image when they depend on the runtime `{{WORKSPACE_NAME}}` placeholder. - Remove any stale literal `{{WORKSPACE_NAME}}` directory left over from older images at container startup. 3. Update `instance_service.py` to pass `REPO_NAME` and `WORKSPACE_NAME` into manifest compilation. 4. Remove the explicit repo mount from the built-in `pi-agent` manifest so the repo mount is synthesized by `compile_compose` rather than depending on tool config. Add a follow-up Alembic data migration that strips the `source_type: repo` mount from the manifest. 5. Add `_get_repository_mount_name()` helper to derive the workspace directory name from the repository's remote URL (matching standard `git clone` behavior) and fall back to the user-provided repository name. 6. Update unit tests for the new behavior. ## Affected files - `apps/api/alembic/versions/2026_06_14_182955_fix_pi_agent_home_directory_mount.py` - `apps/api/alembic/versions/2026_06_15_090500_remove_pi_agent_explicit_repo_mount.py` - `apps/api/src/services/build/manifest_compiler.py` - `apps/api/src/services/tool/instance_service.py` - `apps/api/tests/unit/test_manifest_compiler.py` - `apps/api/tests/unit/test_instance_service.py` - `apps/api/tests/unit/test_alembic_migrations.py` ## Verification - `pytest apps/api/tests/unit/test_manifest_compiler.py` - `pytest apps/api/tests/unit/test_alembic_migrations.py` - `ruff`, `mypy`, `npm run typecheck`, `npm run lint`