diff --git a/nix/agent-modules/user.nix b/nix/agent-modules/user.nix index ee083159..79ce7ada 100644 --- a/nix/agent-modules/user.nix +++ b/nix/agent-modules/user.nix @@ -195,6 +195,13 @@ in : > "$marker" fi fi + # Recursive chown of a dir the agent must own; never fails the script. + # A failure logs chown's first stderr line plus the line count, since a + # walk over a large tree can emit one line per file. + heal_own_dir() { + healErr=$(chown -hR "$userName:$userName" "$1" 2>&1) \ + || echo "hive-agent-user-migrate: could not chown $1 to $userName ($(printf '%s\n' "$healErr" | wc -l) error lines, first: $(printf '%s\n' "$healErr" | head -n 1))" >&2 + } # Scope state + harness + work chowns to THIS container's own dirs only. # The glob `/agents/*/state` also matches other agents' state dirs # bind-mounted into a `ManageRootAgent` holder's container, which @@ -204,10 +211,10 @@ in # kept broad because the holder legitimately owns the proposed-config # repos it edits. if [ -d "/agents/$userName/state" ]; then - chown -hR "$userName:$userName" "/agents/$userName/state" 2>/dev/null || true + heal_own_dir "/agents/$userName/state" fi if [ -d "/agents/$userName/harness" ]; then - chown -hR "$userName:$userName" "/agents/$userName/harness" 2>/dev/null || true + heal_own_dir "/agents/$userName/harness" fi # work/ holds large build trees and the agent creates everything in # it, so a full walk is only needed when the top dir is not already @@ -215,7 +222,7 @@ in # and an agent uid that changed. Otherwise activation does one stat. if [ -d "/agents/$userName/work" ]; then if [ "$(stat -c %U "/agents/$userName/work")" != "$userName" ]; then - chown -hR "$userName:$userName" "/agents/$userName/work" 2>/dev/null || true + heal_own_dir "/agents/$userName/work" fi fi # The socket dir is bind-mounted from the host, where hive-priv creates @@ -235,21 +242,22 @@ in # The proposed-config repo is RW-mounted into the editing agent and # owned by it; hive-c0re only pulls from it. Heal # it to this user too — same as state/harness. In an agent's own - # container its config is RO-mounted, so the chown there just fails - # harmlessly (|| true). + # container its config is RO-mounted, so the chown there fails with + # EROFS on every run: deliberately silent, unlike the dirs above. for configDir in /agents/*/config; do [ -d "$configDir" ] || continue chown -hR "$userName:$userName" "$configDir" 2>/dev/null || true done if [ -d "$homeDir/.claude" ]; then - chown -hR "$userName:$userName" "$homeDir/.claude" 2>/dev/null || true + heal_own_dir "$homeDir/.claude" # 0755 so hive-core (a different unix user) can list the dir and # detect a valid claude session. Credential files inside are 0600 # so secrets stay private regardless of the directory mode. # ensure_claude_dir sets 0755 on creation but cannot re-chmod after # hive-agent-user-migrate chowns the dir to the agent user; this # activation script runs as root and handles the correction. - chmod 755 "$homeDir/.claude" 2>/dev/null || true + chmod 755 "$homeDir/.claude" \ + || echo "hive-agent-user-migrate: could not chmod 755 $homeDir/.claude; hive-core cannot detect the claude session" >&2 fi ''; };