The compose file was forcing the container to run as uid 1001, so the
entrypoint could not create /workspace even with sudo configured.
- Remove Dockerfile USER directive so containers start as root
- Make compile_compose use user: 0:0 when the manifest declares a user
- Make the entrypoint drop to the container user via after setup,
preserving environment variables and command arguments
- Update unit tests to assert root startup and privilege drop
Quality gates:
- pytest tests/unit: 210 passed
- ruff: clean on changed files
- mypy: clean on changed files
The previous ordering checked SUDO before checking if the process was
already running as root. When Docker starts the container with a
non-root user, SUDO may be empty, but the real fix is that the
entrypoint should try root first (e.g. when the image is started as
root) and only then fall back to sudo.
- Reorder symlink creation logic: root first, then sudo, then best-effort
- Update unit test to assert root is checked before sudo
Quality gates:
- pytest tests/unit: 208 passed
- ruff: clean on changed files
- mypy: clean on changed files
The previous commit moved the pi-agent repo mount from /workspace to
/home/user/{repo_name}. This exposed a permission bug: the Dockerfile
creates /workspace as a root-owned symlink in the image, and the
non-root entrypoint could not replace it because / is owned by root.
- Update compile_entrypoint to recreate /workspace via sudo when running
as the container user, or directly when running as root
- Add unit test covering sudo/root symlink creation
- Update OpenSpec change docs with the additional root cause
Quality gates:
- pytest tests/unit: 208 passed
- ruff: clean on changed files
- mypy: clean on changed files
- alembic heads: single head
- Add Alembic migration to update built-in pi-agent manifest:
* repo mount target from /workspace to ~/{{WORKSPACE_NAME}}
* keep /workspace as compatibility symlink via working_dir
* update startup chown target to $HOME/$WORKSPACE_NAME
- Pass REPO_NAME and WORKSPACE_NAME to compile_compose from instance_service
- Substitute {{WORKSPACE_NAME}} in manifest mount targets and expose it as
a container env var so the entrypoint can create the /workspace symlink
- Generate entrypoint workspace symlink from runtime WORKSPACE_NAME env var
- Install npm_global packages into {home_dir}/.npm-global with PATH so the
non-root container user can update global packages
- Update manifest compiler unit tests for the new behavior
Quality gates:
- pytest tests/unit: 207 passed
- ruff: clean on changed files
- mypy: clean on changed files
- alembic heads: single head
Root cause: manifest-based Dockerfile created the home directory and
chowned only the home root. Files/directories copied from /etc/skel by
useradd -m (or created later by root) remained root-owned, so apps like
ranger failed when writing to ~/.config.
Changes:
- manifest_compiler.py: recursive chown of the home directory after
useradd so /etc/skel contents are owned by the container user
- Pre-create .config, .local/share, .cache and chown them to the user
so first-run apps have writable directories immediately
- Add unit test verifying the Dockerfile emits the expected user/home
setup and config directory creation
Quality gates: py_compile all backend files pass, test file compiles,
tsc --noEmit pass, npm run build pass, 82/82 web tests pass
Note: pytest not available in this shell; backend unit test was not
executed but follows existing project conventions.
- Add expand_container_path() helper that resolves ~/ and $HOME/ prefixes
- Add get_manifest_home_dir() to compute /home/{user.name} or /root from manifest
- Set ENV HOME=... and ENV USER=... in generated Dockerfile for runtime compatibility
- Pass home_dir through instance creation and startup pipeline
- Expand mount targets in apply_resolved_profile() for regular profile mounts
- Expand mapping targets in _resolve_git_mount_mappings() for git mounts
- Expand working_directory and volume targets in _modify_compose_file()
- Update _prepare_manifest_instance to return home_dir alongside image tag
- Fetch tool_type early in start_instance to determine home_dir before profile application
Quality gates: pytest 188 passed, frontend typecheck clean
Addresses: home-path-expansion
- Add ToolDefinitionManifest model with base image versioning
- Add manifest compiler: Dockerfile + Compose generation from JSON manifests
- Add permission fixer: post-start chown/chmod for mount policies
- Add tool definition CRUD API with live compile preview endpoint
- Integrate manifest-based startup flow in start_instance
- Add Alembic migration with data conversion for pi-agent
- Add 48 unit tests for manifest compiler, permission fixer, docker service
- Keep backward compatibility with legacy dockerfile_template/compose_template
Migration: applied successfully. Pi-agent converted to manifest.
Quality gates: pytest (146 passed, 4 pre-existing unrelated failures)