Home Directory Management in dify-agent#
The HOME environment variable for shell commands in dify-agent is not owned by the shell layer. It is derived entirely from the RuntimeLease that the runtime backend hands to DifyShellLayer. Understanding this ownership boundary is the key to debugging any home-directory-related failure.
Note: Home directory management, ownership, and isolation vary by backend. This document describes the behavior for the Local, Enterprise, and E2B backends. For the OpenShell backend's architecture, see the OpenShell Home Directory Management section below.
How HOME is constructed#
When a shell command runs, DifyShellLayer._build_shell_command_env() sets env["HOME"] to the home_dir path from the active RuntimeLease.layout. The shell layer never computes or stores a home path itself β it only reads what the runtime backend provides.
For the Local, Enterprise, and E2B backends, home paths are built by LocalExecutionBindingBackend._home_dir() (Local uses a per-binding materialized directory; Enterprise and E2B follow similar patterns for their respective resources):
{materialized_home_root}/{binding_id}
For Local, the materialized_home_root is configured via DIFY_AGENT_LOCAL_SANDBOX_MATERIALIZED_HOME_ROOT (defaulting to /home/dify) . ServerSettings.local_sandbox_materialized_home_root mirrors this field and passes it through to RuntimeBackendSettings during server startup .
The runtime backend validates that the root is an absolute POSIX path at startup; an invalid value raises a ValueError during settings instantiation .
macOS /home compatibility issue#
On macOS, /home is a root-owned symlink to /System/Volumes/Data/home . A non-root dify-agent process cannot create subdirectories there, so any home-directory operation fails with a permission error.
The original hardcoded default of /home was reported in issue #38902 and fixed in PR #38903, which introduced a configurable shell_home_root. In the current architecture this surfaced as the local_sandbox_materialized_home_root setting β set it to a writable path for local macOS development:
DIFY_AGENT_LOCAL_SANDBOX_MATERIALIZED_HOME_ROOT=/tmp/dify-agent-homes
The Operations Guide similarly documents that DIFY_AGENT_LOCAL_SANDBOX_ENDPOINT and related settings replace the older, shell-owned home root: "There is no compatibility setting for the removed shell-provider selector or Shell-owned Home root."
Evolution: shell-owned β runtime-backend-controlled#
| Era | Owner of HOME | Configuration knob |
|---|---|---|
| Original | DifyShellLayer (hardcoded /home/<agent_id>) | None |
| PR #38903 (July 2026) | DifyShellLayer (configurable shell_home_root) | DIFY_AGENT_SHELL_HOME_ROOT env var |
| Current | DifyRuntimeLayer / LocalExecutionBindingBackend | DIFY_AGENT_LOCAL_SANDBOX_MATERIALIZED_HOME_ROOT |
After the refactor, DifyShellLayer no longer configures or validates home roots. Its docstring is explicit: "Commands, files, Home, and cwd come only from DifyRuntimeLayer.lease." The PR-era shell_home_root field and _normalize_shell_home_root() helper are not present in the current codebase β a code search confirms they were superseded when home lifecycle was moved into the runtime backend .
Path isolation (Landlock) β Local, Enterprise, and E2B backends#
On Local, Enterprise, and E2B backends, the runtime grants each agent read-write access exclusively to its own $HOME. On Linux β₯ 5.13, the shellctl-runner child process applies Landlock to enforce this at the kernel level, denying access to other agents' home directories, /tmp, and most host paths . This is why the materialized home root must be a path the shellctl process can reach and write; on macOS, Landlock is not enforced but the permission issue on /home still applies at the filesystem level.
The OpenShell backend uses a different isolation model; see the OpenShell Home Directory Management section below.
Key files#
| File | Purpose |
|---|---|
runtime_backend/local.py | _home_dir() β computes per-binding home path |
runtime_backend/profile.py | Default roots and RuntimeBackendSettings validation |
server/settings.py | local_sandbox_materialized_home_root server setting |
layers/shell/layer.py | _build_shell_command_env() β reads home_dir from lease |
| Shell Layer user manual | RuntimeLease.layout.home_dir contract description |
| Operations Guide | Full env var reference table |
runtime_backend/openshell.py | OpenShell backend implementation and snapshot management |
| OpenShell Operations Guide | OpenShell configuration, deployment, and validation |
OpenShell Home Directory Management#
The OpenShell backend uses a different architecture for home directory management compared to Local, Enterprise, and E2B:
Per-sandbox home directories#
OpenShell creates one sandbox per Execution Binding (one sandbox represents both Binding and Workspace). Each sandbox has its own isolated home directory at the standard /home/dify path. Unlike the Local backend, where multiple Bindings can run in the same shellctl namespace with separate materialized home directories, OpenShell sandboxes are entirely separate gateway-managed containers.
Home Snapshot storage#
Home Snapshots are stored as directory copies on an operator-provided shared volume. The operator configures DIFY_AGENT_OPENSHELL_SHARED_VOLUME_SNAPSHOTS (via DIFY_AGENT_OPENSHELL_DRIVER_CONFIG), which must mount a shared volume into every sandbox. Snapshots live under:
<shared_mount_path>/home-snapshots/<tenant-digest>/<snapshot-name>
Snapshots are tenant-specific: the <tenant-digest> is a deterministic hash of the tenant ID, ensuring each tenant's snapshots are isolated within the shared volume. Production multi-tenant deployments must use one OpenShell workspace and one dedicated shared volume per tenant to maintain security boundaries.
Path isolation#
OpenShell applies best-effort Landlock isolation at the gateway level. The sandbox policy sent during creation includes:
- Read-only paths:
/usr,/lib,/proc,/dev/urandom,/app,/etc,/var/log - Read-write paths:
/tmp,/dev/null,/dev/pts, the tenant's snapshot directory underhome-snapshots/<tenant-digest>/, and the per-sandbox home and workspace
This differs from Local/Enterprise/E2B in several ways:
- Gateway-managed policy: The OpenShell gateway enforces the policy rather than shellctl's own Landlock layer.
shellctlis bootstrapped withSHELLCTL_ENABLE_PATH_ISOLATION=falseto avoid stacking two Landlock policies. - Writable
/tmp: OpenShell grants read-write access to/tmp(Local denies it). - Tenant snapshot isolation: Each sandbox can only access its tenant's snapshot directory, not the entire shared volume.
- Best-effort compatibility: The policy specifies
landlock: compatibility="best_effort", meaning it applies on kernels that support Landlock but does not fail on older systems.
Ownership and lifecycle#
Unlike Local, where the dify-agent process directly manages home directories on the shellctl filesystem, OpenShell sandboxes are owned and managed by the gateway:
- Sandbox creation, start, and stop are gateway operations reached through the gRPC API.
- shellctl is bootstrapped idempotently via gateway
execon every acquire. - The sandbox remains running after lease release (lightweight release model); it is only stopped or destroyed on explicit Binding destroy operations.
- Snapshot deletion runs in a short-lived maintenance sandbox that is granted write access to the tenant's snapshot root directory.