@@ -1058,7 +1058,13 @@ function () use ( $primary_path, $wt_path, $force, $wt_handle ) {
10581058 $ result = $ this ->run_git ($ primary_path , $ cmd );
10591059
10601060 if ( is_wp_error ($ result ) ) {
1061- return $ result ;
1061+ return $ this ->worktree_git_unavailable_with_host_commands (
1062+ $ result ,
1063+ 'Remove workspace worktree ' ,
1064+ array (
1065+ sprintf ('git -C %s %s ' , escapeshellarg ($ primary_path ), $ cmd ),
1066+ )
1067+ );
10621068 }
10631069
10641070 WorktreeContextInjector::forget_metadata ($ wt_handle );
@@ -1083,10 +1089,12 @@ function () use ( $primary_path, $wt_path, $force, $wt_handle ) {
10831089 /**
10841090 * Prune stale worktree registry entries across all primaries.
10851091 *
1086- * @return array{success: bool, pruned: array}|\WP_Error
1092+ * @return array{success: bool, pruned: array, skipped?: array, next_commands?: array, inventory?: array }|\WP_Error
10871093 */
10881094 public function worktree_prune (): array |\WP_Error {
1089- $ pruned = array ();
1095+ $ pruned = array ();
1096+ $ skipped = array ();
1097+ $ next_commands = array ();
10901098
10911099 if ( ! is_dir ($ this ->workspace_path ) ) {
10921100 return array (
@@ -1110,6 +1118,15 @@ public function worktree_prune(): array|\WP_Error {
11101118 fn () => $ this ->run_git ($ primary_path , 'worktree prune -v ' )
11111119 );
11121120 if ( is_wp_error ($ result ) ) {
1121+ if ( 'datamachine_workspace_git_unavailable ' === $ result ->get_error_code () ) {
1122+ $ skipped [] = array (
1123+ 'repo ' => $ entry ,
1124+ 'primary_path ' => $ primary_path ,
1125+ 'reason ' => $ result ->get_error_message (),
1126+ );
1127+ $ next_commands [] = sprintf ('git -C %s worktree prune -v ' , escapeshellarg ($ primary_path ));
1128+ continue ;
1129+ }
11131130 return $ result ;
11141131 }
11151132 $ pruned [] = $ entry ;
@@ -1121,12 +1138,40 @@ public function worktree_prune(): array|\WP_Error {
11211138 }
11221139
11231140 return array (
1124- 'success ' => true ,
1125- 'pruned ' => $ pruned ,
1126- 'inventory ' => $ refresh ,
1141+ 'success ' => true ,
1142+ 'pruned ' => $ pruned ,
1143+ 'skipped ' => $ skipped ,
1144+ 'next_commands ' => array_values (array_unique ($ next_commands )),
1145+ 'inventory ' => $ refresh ,
11271146 );
11281147 }
11291148
1149+ /**
1150+ * Attach host-shell remediation commands to local-git-unavailable worktree errors.
1151+ *
1152+ * @param \WP_Error $error Original git error.
1153+ * @param string $operation Human-readable operation.
1154+ * @param array<int,string> $next_commands Exact commands to run in a host shell.
1155+ * @return \WP_Error
1156+ */
1157+ private function worktree_git_unavailable_with_host_commands ( \WP_Error $ error , string $ operation , array $ next_commands ): \WP_Error {
1158+ if ( 'datamachine_workspace_git_unavailable ' !== $ error ->get_error_code () ) {
1159+ return $ error ;
1160+ }
1161+
1162+ $ data = (array ) $ error ->get_error_data ();
1163+ $ data ['operation ' ] = $ operation ;
1164+ $ data ['next_commands ' ] = array_values (array_filter (array_map ('strval ' , $ next_commands )));
1165+ $ data ['hint ' ] = 'Run the listed command from a host shell with local git access, then rerun workspace worktree prune to refresh DMC inventory. ' ;
1166+
1167+ $ message = $ error ->get_error_message ();
1168+ if ( ! empty ($ data ['next_commands ' ][0 ]) ) {
1169+ $ message .= ' Host command: ' . $ data ['next_commands ' ][0 ];
1170+ }
1171+
1172+ return new \WP_Error ($ error ->get_error_code (), $ message , $ data );
1173+ }
1174+
11301175
11311176 /**
11321177 * Resolve a sensible default base for new branches.
0 commit comments