diff --git a/docker/stage2-hook.sh b/docker/stage2-hook.sh index df7a1c83b5..899c8e86ac 100755 --- a/docker/stage2-hook.sh +++ b/docker/stage2-hook.sh @@ -288,8 +288,10 @@ fi # Always reset ownership of $HERMES_HOME/cron on every boot for the same # docker-exec/root-write reason as profiles/. The cron scheduler state # (jobs.json) must stay readable by the unprivileged hermes runtime even -# after root-context maintenance commands or scheduler writes. -if [ -d "$HERMES_HOME/cron" ]; then +# after root-context maintenance commands or scheduler writes. Skip the +# recursive walk when the tree is already owned correctly (same warm-boot +# gate as profiles/). +if [ -d "$HERMES_HOME/cron" ] && tree_has_non_hermes_owner "$HERMES_HOME/cron"; then chown_hermes_tree "$HERMES_HOME/cron" fi @@ -315,13 +317,14 @@ fi # silently leaving the approved user unauthorized (#10270). The targeted # data-volume chown above only runs when the top-level $HERMES_HOME is # mis-owned, so warm boots skip it — this block makes a container restart -# self-heal. Tiny directory (a handful of small JSON files), so the cost -# is negligible. -if [ -d "$HERMES_HOME/platforms/pairing" ]; then +# self-heal. Tiny directory (a handful of small JSON files), so even the +# ownership pre-scan is negligible; gated for consistency with profiles/ +# and cron/. +if [ -d "$HERMES_HOME/platforms/pairing" ] && tree_has_non_hermes_owner "$HERMES_HOME/platforms/pairing"; then chown_hermes_tree "$HERMES_HOME/platforms/pairing" fi # Legacy location (pre-consolidated layout). -if [ -d "$HERMES_HOME/pairing" ]; then +if [ -d "$HERMES_HOME/pairing" ] && tree_has_non_hermes_owner "$HERMES_HOME/pairing"; then chown_hermes_tree "$HERMES_HOME/pairing" fi diff --git a/tests/tools/test_stage2_hook_symlink_chown.py b/tests/tools/test_stage2_hook_symlink_chown.py index 8c2a95233f..fd68d78def 100644 --- a/tests/tools/test_stage2_hook_symlink_chown.py +++ b/tests/tools/test_stage2_hook_symlink_chown.py @@ -122,3 +122,7 @@ def test_stage2_skips_recursive_repairs_when_tree_is_already_owned( assert "tree_has_non_hermes_owner() {" in stage2_text assert 'if [ -e "$HERMES_HOME/$sub" ] && tree_has_non_hermes_owner "$HERMES_HOME/$sub"; then' in stage2_text assert 'if [ -d "$HERMES_HOME/profiles" ] && tree_has_non_hermes_owner "$HERMES_HOME/profiles"; then' in stage2_text + # Sibling every-boot chown blocks carry the same warm-boot gate. + assert 'if [ -d "$HERMES_HOME/cron" ] && tree_has_non_hermes_owner "$HERMES_HOME/cron"; then' in stage2_text + assert 'if [ -d "$HERMES_HOME/platforms/pairing" ] && tree_has_non_hermes_owner "$HERMES_HOME/platforms/pairing"; then' in stage2_text + assert 'if [ -d "$HERMES_HOME/pairing" ] && tree_has_non_hermes_owner "$HERMES_HOME/pairing"; then' in stage2_text